پرش به مطلب اصلی

ساختارهای کنترل

شرطی‌ها

if

یک دستور if بررسی می‌کند که آیا چیزی درست است و در صورت درست بودن، کاری انجام می‌دهد.

new
a = 5;
if (a == 5)
{
print("a is 5");
}

کد داخل پرانتزهای بعد از "if" شرط نامیده می‌شود، چیزهای مختلفی وجود دارد که می‌توانید برای آن‌ها تست کنید (عملگرها را ببینید).

در مثال بالا، هم "a" و هم 5 نمادهایی هستند، توابع نیز می‌توانند نماد باشند:

if (SomeFunction() == 5)
{
print("SomeFunction() is 5");
}

این کد مقدار بازگشتی SomeFunction (که در زیر توضیح داده شده) را در برابر 5 تست می‌کند.

همچنین می‌توانید بررسی‌ها را ترکیب کنید تا چندین چیز را بررسی کنید:

new
a = 5,
b = 3;
if (a == 5 && b != 3)
{
print("Won't be printed");
}

این مثال بررسی می‌کند که a برابر با 5 باشد و b برابر با 3 نباشد، اما چون b برابر 3 است، بررسی شکست می‌خورد.

new
a = 5,
b = 3;
if (a == 5 || b != 3)
{
print("Will be printed");
}

این مثال بررسی می‌کند که a برابر با 5 باشد یا b برابر با 3 نباشد، b برابر 3 است پس آن قسمت شکست می‌خورد اما a برابر 5 است پس آن قسمت درست است. ما از || (یا) استفاده می‌کنیم پس فقط یک قسمت نیاز دارد درست باشد (اگر هر دو درست باشند، عبارت همچنان درست است، این کمی متفاوت از معنای زبان‌شناختی "یا" است که به معنای فقط یکی یا دیگری است)، پس دستور if درست است.

همچنین امکان زنجیره‌ای کردن مقایسه‌ها بدون AND کردن صریح دو مقایسه مختلف وجود دارد.

new
idx = 3;

if (0 < idx < 5)
{
print("idx is greater than 0 and less than 5!");
}

عملگرها

موارد زیر نمادهایی هستند که می‌توانید در مقایسه‌ها استفاده کنید به همراه توضیحاتشان. برخی از آن‌ها قبلاً در مثال‌ها استفاده شده‌اند.

عملگرمعنیاستفاده
==چپ برابر با راستif (Left == Right)
!=چپ برابر با راست نیستif (Left != Right)
>چپ بزرگتر از راستif (Left > Right)
>=چپ بزرگتر یا مساوی راستif (Left >= Right)
<چپ کوچکتر از راستif (Left < Right)
<=چپ کوچکتر یا مساوی راستif (Left <= Right)
&&وif (Left && Right)
||یاif (Left || Right)
!نهif (!Variable)
نه یا (nor)if (!(Left || Right))
نه و (nand)if (!(Left && Right))
یا انحصاری (xor, eor) - فقط یکی یا دیگری درست است، نه هر دوif (!(Left && Right) && (Left ||Right))
نه یا انحصاری (nxor, neor) - هر دو یا هیچکدام درست استif ((Left && Right) || !(Left || Right))

پرانتزها

جنبه اصلی دیگر دستورات if، پرانتزها هستند که ترتیب انجام کارها را کنترل می‌کنند:

new
a = 3,
b = 3,
c = 1;
if (a == 5 && b == 3 || c == 1)
{
print("Will this be called?");
}

دو روش برای نگاه کردن به عبارت بالا وجود دارد:

if ((a == 5 && b == 3) || c == 1)

و:

if (a == 5 && (b == 3 || c == 1))

نسخه اول بررسی می‌کند که آیا a برابر 5 و b برابر 3 است، اگر این نادرست باشد (یعنی یکی یا هر دوی آن‌ها مقدار مربوطه خودشان نباشند) بررسی خواهد کرد که آیا c برابر 1 است. (a == 5 && b == 3) نادرست است همان‌طور که باید از بالا بدانید، پس آن گروه را با FALSE جایگزین می‌کنیم:

if (FALSE || c == 1)

می‌دانیم FALSE نمی‌تواند درست باشد (چون به تعریف درست نیست)، اما c برابر 1 است پس آن نیمه درست است و چون ما از OR در میان استفاده می‌کنیم، کل عبارت درست است.

نسخه دوم بررسی می‌کند که آیا a برابر 5 است، اگر است بررسی می‌کند که آیا b برابر 3 یا c برابر 1 است. بازی قسمت a == 5 را ابتدا انجام می‌دهد اما برای وضوح آن را معکوس انجام می‌دهیم. (b == 3 || c == 1) درست است، هر دو نیمه درست هستند، اگرچه فقط یکی نیاز دارد درست باشد، پس وارد دستور if مان می‌شویم:

if (a == 5 && TRUE)

(a == 5) نادرست است، چون a برابر 3 است، پس داریم:

if (FALSE && TRUE)

واضح است که FALSE نادرست است پس آن عبارت نمی‌تواند درست باشد، پس بررسی شکست خواهد خورد.

این مثال کوچک نشان می‌دهد که چگونه استفاده از پرانتز می‌تواند نتیجه یک بررسی را تغییر دهد، بدون پرانتز کامپایلر اولین روش از دو روش نشان داده شده را انجام خواهد داد اما این نمی‌تواند همیشه تضمین شود پس باید همیشه از پرانتز استفاده کنید، حتی اگر فقط برای روشن‌سازی آنچه در حال رخ دادن است برای افراد دیگر باشد.

  • (b != 3) در مثال OR در واقع شکست نمی‌خورد چون هرگز فراخوانی نمی‌شود، کامپایلر کد را با استفاده از چیزی به نام short-circuiting مرتب می‌کند، چون قسمت اول از قبل درست است، بررسی قسمت دوم فایده‌ای ندارد چون بر نتیجه نهایی تأثیری نخواهد داشت، اما اگر آن را بررسی می‌کرد، شکست می‌خورد.

else

else اساساً کاری انجام می‌دهد اگر یک بررسی if شکست بخورد:

new
a = 5;
if (a == 3) // False
{
print("Won't be called");
}
else
{
print("Will be called as the check failed");
}

else if

یک else if بررسی‌ای است که اگر اولین بررسی if شکست بخورد برای بررسی چیز دیگری رخ می‌دهد:

new
a = 5;
if (a == 1)
{
print("Will be called if a is 1");
}
else if (a == 5)
{
print("Will be called if a is 5");
}
else
{
print("All other numbers");
}

می‌توانید هر تعداد از این‌ها که می‌خواهید داشته باشید (فقط می‌توانید یک if و یک else در یک گروه از بررسی‌ها داشته باشید):

new
a = 4;
if (a == 1)
{
// False
}
else if (a == 2)
{
// False
}
else if (a == 3)
{
// False
}
else if (a == 4)
{
// True
}
else
{
// False
}

else if ها فقط مقدار را همان‌طور که زمانی که if ها شروع شدند بودند بررسی می‌کنند، پس نمی‌توانید این کار را بکنید:

new
a = 5;
if (a == 5)
{
// Will be called
a = 4;
}
else if (a == 4)
{
// Won't be called because the first check didn't fail, even though a is now 4
}

برای دور زدن این مشکل باید else if را به یک if تبدیل کنید.

?:

'?' و ':' با هم عملگر سه‌گانه نامیده می‌شوند، آن‌ها اساساً مثل یک دستور if داخل یک دستور دیگر عمل می‌کنند:

new
a,
b = 3;
if (b == 3)
{
a = 5;
}
else
{
a = 7;
}

این یک مثال ساده برای تخصیص یک متغیر بر اساس متغیر دیگر است، اما می‌تواند بسیار کوتاه‌تر شود:

new
a,
b = 3;
a = (b == 3) ? (5) : (7);

قسمت قبل از '?' شرطی است، این دقیقاً مثل یک شرطی معمولی است. قسمت بین '?' و ':' مقداری است که اگر شرط درست باشد برگردانده شود، قسمت دیگر مقداری است که اگر شرط نادرست باشد برگردانده شود. می‌توانید آن‌ها را مثل else if ها روی هم انباشته کنید:

new
a,
b = 3;
if (b == 1)
{
a = 2;
}
else if (b == 2)
{
a = 3;
}
else if (b == 3)
{
a = 4;
}
else
{
a = 5;
}

می‌تواند به این شکل نوشته شود:

new
a,
b = 3;
a = (b == 1) ? (2) : ((b == 2) ? (3) : ((b == 3) ? (4) : (5)));

این در واقع بیشتر شبیه انجام این کار است:

new
a,
b = 3;
if (b == 1)
{
a = 2;
}
else
{
if (b == 2)
{
a = 3;
}
else
{
if (b == 3)
{
a = 4;
}
else
{
a = 5;
}
}
}

اما آن‌ها معادل هستند (در هر حال در این مثال).

حلقه‌ها

While

حلقه‌های "while" کاری انجام می‌دهند در حالی که شرط مشخص شده درست است. یک شرط دقیقاً همان فرمت شرط در یک دستور if است، فقط بارها بررسی می‌شود و کد در صورت درست بودن آن در هر بار بررسی انجام می‌شود:

new
a = 9;
while (a < 10)
{
// Code in the loop
a++;
}
// Code after the loop

آن کد بررسی خواهد کرد که آیا 'a' کمتر از 10 است. اگر است، کد داخل آکولادها (a++;) اجرا خواهد شد، بنابراین 'a' افزایش می‌یابد. وقتی آکولاد بسته رسیده شود، اجرای کد به بررسی برمی‌گردد و دوباره آن را انجام می‌دهد، این بار بررسی شکست خواهد خورد چون 'a' برابر 10 است و اجرا به بعد از حلقه خواهد پرید. اگر 'a' به عنوان 8 شروع می‌کرد، کد دو بار اجرا می‌شد و غیره.

for()

یک حلقه "for" اساساً یک حلقه "while" فشرده است. یک دستور "for" سه بخش دارد؛ مقداردهی اولیه، شرط و نهایی‌سازی. به عنوان یک حلقه "for"، مثال "while" بالا این‌طور نوشته می‌شود:

for (new a = 9; a < 10; a++)
{
// Code in the loop
}
// Code after the loop

این یک کد ساده برای حلقه زدن روی همه بازیکن‌ها است:

for(new i,a = GetMaxPlayers(); i < a; i++)
{
if (IsPlayerConnected(i))
{
//do something
}
}

هر یک از شرایط می‌تواند با قرار ندادن کد در آن‌ها حذف شود:

new
a = 9;
for ( ; a < 10; )
{
// Code in the loop
a++;
}
// Code after the loop

این مثال کمی آسان‌تر نشان می‌دهد که چگونه یک حلقه "for" با یک حلقه "while" مطابقت دارد. دو تفاوت بسیار جزئی بین دو حلقه "for" داده شده وجود دارد. اول اینکه مثال دوم 'a' را خارج از حلقه "for" اعلان می‌کند، این بدان معناست که می‌تواند خارج از حلقه "for" استفاده شود، در مثال اول دامنه 'a' (بخشی از کد که متغیر در آن وجود دارد) فقط داخل حلقه است. تفاوت دوم این است که a++ در مثال دوم در واقع کمی قبل از a++ در مثال اول انجام می‌شود، 99% از زمان این تفاوتی ایجاد نمی‌کند، تنها زمانی که مهم است زمانی است که از 'continue' استفاده می‌کنید (زیر را ببینید)، 'continue' a++ را در مثال اول فراخوانی می‌کند، اما در مثال دوم آن را رد می‌کند.

do-while

یک حلقه do-while یک حلقه while است که شرط بعد از کد داخل حلقه به جای قبل از آن می‌آید. این بدان معناست که کد داخل همیشه حداقل یک بار اجرا خواهد شد چون قبل از انجام بررسی انجام می‌شود:

new
a = 10;
do
{
// Code inside the loop
a++;
}
while (a < 10); // Note the semi-colon
// Code after the loop

اگر این یک حلقه while استاندارد بود، a افزایش نمی‌یافت چون بررسی (a < 10) نادرست است، اما اینجا قبل از بررسی افزایش می‌یابد. اگر a به عنوان 9 شروع می‌کرد، حلقه نیز فقط یک بار انجام می‌شد، 8 - دو بار و غیره.

if-goto

این اساساً همان چیزی است که حلقه‌های بالا به آن کامپایل می‌شوند، استفاده از goto عموماً دلسرد کننده است اما جالب است که دقیقاً ببینید یک حلقه چه کاری انجام می‌دهد:

new
a = 9;

loop_start:
if (a < 10)
{
// Code in the loop
a++;
goto loop_start;
}
// Code after the loop

OBOE

OBOE مخفف Off By One Error است. این یک اشتباه بسیار رایج است که یک حلقه یکی بیش از حد یا یکی کم از حد اجرا می‌شود. مثلاً:

new
a = 0,
b[10];
while (a <= sizeof (b))
{
b[a] = 0;
}

این مثال بسیار ساده یکی از رایج‌ترین OBOE ها را نشان می‌دهد، در نگاه اول مردم ممکن است فکر کنند این روی همه محتویات b حلقه خواهد زد و آن‌ها را 0 خواهد کرد، اما این حلقه در واقع 11 بار اجرا خواهد شد و سعی خواهد کرد به b[10] دسترسی پیدا کند که وجود ندارد (این 11امین slot در b خواهد بود که از 0 شروع می‌شود)، بنابراین می‌تواند انواع مشکلات ایجاد کند. این به عنوان خطای Out Of Bounds (OOB) شناخته می‌شود.

باید مخصوصاً مراقب OBOE ها باشید وقتی از حلقه‌های do-while استفاده می‌کنید چون آن‌ها همیشه حداقل یک بار اجرا می‌شوند.

Switch

switch

یک دستور switch اساساً یک سیستم ساختاریافته if/else if/else است (شبیه به اینکه for یک while ساختاریافته است). آسان‌ترین راه برای توضیح آن با یک مثال است:

new
a = 5;
switch (a)
{
case 1:
{
// Won't be called
}
case 2:
{
// Won't be called
}
case 5:
{
// Will be called
}
default:
{
// Won't be called
}
}

این از نظر عملکردی معادل این است:

new
a = 5;
if (a == 1)
{
// Won't be called
}
else if (a == 2)
{
// Won't be called
}
else if (a == 5)
{
// Will called
}
else
{
// Won't be called
}

اما کمی واضح‌تر است که چه اتفاقی در حال رخ دادن است.

نکته مهمی که باید در اینجا ذکر کرد تفاوت روش‌هایی است که if ها و switch ها پردازش می‌شوند:

switch (SomeFunction())
{
case 1: {}
case 2: {}
case 3: {}
}

این SomeFunction() را یک بار فراخوانی می‌کند و نتیجه آن را 3 بار مقایسه می‌کند.

if (SomeFunction() == 1) {}
else if (SomeFunction() == 2) {}
else if (SomeFunction() == 3) {}

این SomeFunction() را سه بار فراخوانی می‌کند که بسیار ناکارآمد است، یک switch بیشتر شبیه انجام این کار است:

new
result = SomeFunction();
if (result == 1) {}
else if (result == 2) {}
else if (result == 3) {}

برای کسانی از شما که C می‌دانید، switch در PAWN کمی متفاوت است، شرایط فردی fall-through نیستند و توسط آکولادها محدود می‌شوند پس نیازی به دستورات break نیست.

case

دستورات case (قسمت‌های "case X:" از دستور switch) می‌توانند گزینه‌های دیگری غیر از یک عدد تکی داشته باشند. می‌توانید یک مقدار را با لیستی از اعداد مقایسه کنید (جایگزین کردن fall-through در C) یا حتی محدوده‌ای از مقدارها:

case 1, 2, 3, 4:

این case اگر نماد در حال تست 1، 2، 3، یا 4 باشد فعال می‌شود، همان است با انجام:

if (bla == 1 || bla == 2 || bla == 3 || bla == 4)

اما بسیار مختصرتر. اعداد در لیست‌ها نیاز ندارد پشت سر هم باشند، در واقع اگر هستند بهتر است این کار را بکنید:

case 1 .. 4:

این case دقیقاً همان کار بالا را خواهد کرد اما با بررسی محدوده به جای لیست، همان است با انجام:

if (bla >= 1 && bla <= 4)
new
a = 4;
switch (a)
{
case 1 .. 3:
{
}
case 5, 8, 11:
{
}
case 4:
{
}
default:
{
}
}

default

این معادل else در دستورات if است، کاری انجام می‌دهد اگر همه دستورات case دیگر شکست بخورند.

دستورات تک خطی

goto

goto اساساً یک پرش است، بدون سؤال به یک برچسب می‌رود (یعنی هیچ شرطی نیاز ندارد درست باشد). می‌توانید مثالی در بالا در حلقه if-goto ببینید.

goto my_label;

// This section will be jumped over

my_label: // Labels end in a colon and are on their own line

// This section will be processed as normal

استفاده از goto ها به دلیل تأثیرشان بر جریان برنامه به شدت دلسرد کننده است.

break

break از یک حلقه خارج می‌شود و آن را زودتر تمام می‌کند:

for (new a = 0; a < 10; a++)
{
if (a == 5) break;
}

این حلقه 6 بار خواهد رفت اما کد بعد از break فقط 5 بار اجرا خواهد شد.

continue

continue اساساً یک تکرار حلقه را رد می‌کند

for (new a = 0; a < 3; a++)
{
if (a == 1) continue;
printf("a = %d", a);
}

این خروجی زیر را خواهد داد:

a = 0 a = 2

continue اساساً به آکولاد بسته حلقه می‌پرد، همان‌طور که در بالا اشاره شد باید مراقب باشید وقتی continue را با برخی حلقه‌ها استفاده می‌کنید:

new
a = 0;
while (a < 3)
{
if (a == 1) continue;
printf("a = %d", a);
a++;
}

این بسیار شبیه مثال دیگر به نظر می‌رسد اما این بار continue خط a++; را رد خواهد کرد، پس حلقه در یک حلقه بی‌نهایت گیر خواهد کرد چون a همیشه 1 خواهد بود.

return

return یک تابع را متوقف می‌کند و به نقطه‌ای در کد که ابتدا تابع را فراخوانی کرده برمی‌گردد:

main()
{
print("1");
MyFunction(1);
print("3");
}

MyFunction(num)
{
if (num == 1)
{
return;
}
print("2");
}

آن کد خروجی زیر را خواهد داد:

1 3

چون خط print("2"); هرگز رسیده نخواهد شد.

همچنین می‌توانید از return برای برگرداندن یک مقدار استفاده کنید:

main()
{
print("1");
if (MyFunction(1) == 27)
{
print("3");
}
}

MyFunction(num)
{
if (num == 1)
{
return 27;
}
print("2");
return 0;
}

آن کد همان خروجی بالا را خواهد داد، اما توجه کنید که یک return اضافی به انتهای تابع اضافه شده است. انتهای یک تابع یک return ضمنی در آن دارد، اما این return هیچ مقداری ندارد، نمی‌توانید یک مقدار برگردانید و از همان تابع مقداری برنگردانید پس باید صراحتاً یک مقدار برگردانیم.

نمادی که برمی‌گردانید می‌تواند عدد، متغیر یا حتی تابع دیگری باشد (که در این صورت تابع دیگر فراخوانی خواهد شد، مقداری برخواهد گرداند (اگر آن را به عنوان مقدار return استفاده کنید باید مقداری برگرداند) و آن مقدار از تابع اول برگردانده خواهد شد.

همچنین می‌توانید مقدارهای return را برای استفاده بعدی ذخیره کنید:

main()
{
print("1");
new
ret = MyFunction(1);
if (ret == 27)
{
print("3");
}
}

MyFunction(num)
{
if (num == 1)
{
return 27;
}
print("2");
return 0;
}