درس پنجم – متدها
در این قسمت با متدها در زبان C# آشنا میشوید. اهداف این درس به شرح زیر میباشد :
ü درک ساختار یک متد
ü درک تفاوت بین متدهای استاتیک (static methods) و متدهای نمونه (instance)
ü ایجاد نمونه جدید از اشیاء
ü نحوه فراخوانی متدها
ü درک چهار گونه متفاوت پارامترها
ü نحوه استفاده از مرجع this
تا کنون تمامی اعمالی که ما در برنامههایمان انجام میدادیم در متد Main() اتفاق میافتادند. این روش برای برنامههای ساده و ابتدایی که استفاده کردیم مناسب بود، اما اگر برنامهها پیچیدهتر شوند و تعداد کارهای مورد نظر ما گسترش یابد، استفاده از متدها جایگزین روش قبل میگردد. متدها فوقالعاده مفید هستند، چراکه کارها را به بخشهای کوچکتر و مجزا تقیسم میکنند و در نتیجه استفاده از آنها آسانتر خواهد بود.
ساختار کلی یک متد به صورت زیر است :
[attributes][ modifiers] return-type method-name ([ parameters] ) { statements }
دو قسمت attributes و modifiers را در آینده مورد بررسی قرار خواهیم داد. return-type نوعی است یک متد باز میگرداند و میتواند هر یک از انواع تعریف شده زبان C# و یا از انواع تعریف شده توسط کاربر باشد. هر متد با نام آن شناخته میشود. method-name نام انتخابی برنامهنویس برای یک متد است و از طریق همین نام فراخوانی متد انجام میشود. پارامترها (parameters) مولفهها یا متغیرهایی هستند که برای انجام یکسری پردازش به متد ارسال میشوند و از طریق آنها میتوان اطلاعاتی را به متد ارسال و یا از آن دریافت نمود، و در نهایت دستورالعمهای متد، دستورهایی از زبان C# هستند که بوسیله آنها عمل مورد نظر برنامهنویس انجام میشود و عملی است که یک متد آنرا انجام میدهد. مثال 1-5 پیادهسازی یک متد ساده را نمایش میدهد.
using System;
class OneMethod
{
public static void Main()
{
string myChoice;
OneMethod om = new OneMethod();
do
{
myChoice = om.getChoice();
// تصمیمی بر اساس انتخاب کاربر گرفته میشود
switch(myChoice)
{
case "A":
case "a":
Console.WriteLine("You wish to add an address.");
break;
case "D":
case "d":
Console.WriteLine("You wish to delete an address.");
break;
case "M":
case "m":
Console.WriteLine("You wish to modify an address.");
break;
case "V":
case "v":
Console.WriteLine("You wish to view the address list.");
break;
case "Q":
case "q":
Console.WriteLine("Bye.");
break;
default:
Console.WriteLine("{0} is not a valid choice", myChoice);
break;
}
// اجرای برنامه برای دیدن نتایج موقف میشود
Console.WriteLine();
Console.Write("Press Enter key to continue...");
Console.ReadLine();
Console.WriteLine();
} while (myChoice != "Q" && myChoice != "q");
// اجرای برنامه تا زمانیکه کاربر بخواهد ادامه مییابد
}
string getChoice()
{
string myChoice;
// منویی را نمایش میدهد
Console.WriteLine("My Address Book ");
Console.WriteLine("A - Add New Address");
Console.WriteLine("D - Delete Address");
Console.WriteLine("M - Modify Address");
Console.WriteLine("V - View Addresses");
Console.WriteLine("Q - Quit ");
Console.Write("Choice (A,D,M,V,or Q): ");
// ورودی دریافتی از کاربر را بررسی میکند
myChoice = Console.ReadLine();
Console.WriteLine();
return myChoice;
}
}
برنامه مثال 1-5 دقیقا همان برنامه در س 4 است، با این تفاوت که در درس چهارم چاپ منو و دریافت ورودی از کاربر در متد Main() صورت میگرفت در حالیکه در این مثال، این اعمال در یک متد مجزا بنام getChoice() صورت میگیرد. نوع بازگشتی این متد از نوع رشتهای است. از این رشته در دستور switch در متد Main() استفاده میشود. همانطور که ملاحظه مینمایید، پرانتزهای متد getChoice() خالی هستند، یعنی این متد دارای پارامتر نیست، از اینرو هیچ اطلاعاتی به/ از این متد منتقل نمیشود.
درون این متد، ابتدا متغیر myChoice را اعلان نمودهایم. هرچند نام و نوع این متغیر همانند متغیر myChoice موجود در متد Main() است، اما این دو متغیر دو متغیر کاملاً مجزا از یکدیگر میباشند. هر دو این متغیرها، متغیرهای محلی (Local) هستند، از اینرو تنها درون بلوکی که تعریف شدهاند قابل دسترس میباشند. به بیان دیگر این دو متغیر از وجود یکدیگر اطلاعی ندارند.
متد getChoice() منویی را در کنسول نمایش میدهد و ورودی انتخابی کاربر را دریافت مینماید. دستور return دادهها را از طریق متغیر myChoice به متد فراخواننده آن، یعنی Main()، باز میگرداند. توجه داشته باشید که، نوع متغیری که توسط دستور return باز گردانده میشود، باید دقیقاً همانند نوع بازگشتی متد باشد. در این مثال نوع بازگشتی، رشته است.
در C# دو گونه متد وجود دارد. یکی متدهای استاتیک (Static) و دیگری متدهای نمونه (Instance). متدهایی که در اعلان خود شامل کلمه کلیدی static هستند، از نوع استاتیک هستند، بدین معنا که هیچ نمونهای از روی این متد قابل ایجاد نیست و این تنها همین نمونه موجود قابل استفاده است. از روی متدهای استاتیک نمیتوان شیء (Object) ایجاد کرد. در صورتیکه در اعلان متد از کلمه کلیدی static استفاده نشده باشد، متد بعنوان متد نمونه در نظر گرفته میشود، بدین معنا که از روی آن میتوان نمونه ایجاد کرد و شیء تولید نمود. هر یک از اشیاء ایجاد شده از روی این متدها، تمامی عناصر آن متد را دارای میباشند.
در این مثال، چون getChoice() بصورت استاتیک اعلان نشده است، پس باید برای استفاده از آن شیء جدیدی تولید شود. تولید شیء جدید بوسیله OneMethod om = new OneMethod() صورت میپذیرد. در سمت چپ این اعلان، مرجع این شیء جدید، یعنی om، قرار دارد که از نوع OneMethod است. در اینجا توجه به یک نکته بسیار مهم است، om به خودی خود شیء نیست، بلکه میتواند مرجعی به شیای از نوع OneMethod() را در خود نگهدارد. در سمت راست این اعلان، تخصیص شیء جدیدی از نوع OneMethod() به متغیر om صورت گرفته است. کلمه کلیدی new عملگری است که شیء جدیدی را در heap ایجاد مینماید. اتفاقی که اینجا روی داده اینست که نمونه جدیدی از OneMethod() در heap تولید شده و سپس به مرجع om تخصیص داده میشود. حال که نمونهای از متد OneMethod() را به om تخصیص دادهایم، از طریق om میتوانیم با این متد کار نماییم.
متدها، فیلدها و سایر اعضای یک کلاس از طریق عملگر نقطه "." قابل دسترس هستند. هنگامیکه میخواهیم متد getChoice() را فراخوانی کنیم، بوسیله عملگر نقطه از طریق om به آن دسترسی پیدا مینماییم : om.getChoice() . برای نگهداری مقداری که getChoice() بر میگرداند، از عملگر "=" استفاده نمودهایم. رشته بازگشتی از متد getChoice() درون متغیر محلی myChoice متد Main() قرار میگیرد. از این قسمت، اجرای برنامه همانند قبل است.
پارامترهای متد
به مثال 2-5 توجه کنید.
using System;
class Address
{
public string name;
public string address;
}//Addressپایان کلاس
class MethodParams
{
public static void Main()
{
string myChoice;
MethodParams mp = new MethodParams();
do
{
// منویی نمایش داده شده و ورودی از کاربر دریافت میگردد
myChoice = mp.getChoice();
// تصمیمی بر اساس ورودی کاربر گرفته میشود
mp.makeDecision(myChoice);
// جهت دیدن نتایج توسط کاربر، اجرای برنامه موقتا موقف میگردد
Console.Write("Press Enter key to continue...");
Console.ReadLine();
Console.WriteLine();
} while (myChoice != "Q" && myChoice != "q");
// اجرای حلقه تا زمانیکه کاربر بخواهد ادامه پیدا مینماید
}//Mainپایان متد
// نمایش منو و دریافت ورودی از کاربر
string getChoice()
{
string myChoice;
// نمایش منو
Console.WriteLine("My Address Book ");
Console.WriteLine("A - Add New Address");
Console.WriteLine("D - Delete Address");
Console.WriteLine("M - Modify Address");
Console.WriteLine("V - View Addresses");
Console.WriteLine("Q - Quit ");
Console.WriteLine("Choice (A,D,M,V,or Q): ");
// دریافت ورودی کاربر
myChoice = Console.ReadLine();
return myChoice;
}//getChoice()پایان متد
// تصمیمگیری
void makeDecision(string myChoice)
{
Address addr = new Address();
switch(myChoice)
{
case "A":
case "a":
addr.name = "Hadi";
addr.address = "C# Persian";
this.addAddress(ref addr);
break;
case "D":
case "d":
addr.name = "Salehy";
this.deleteAddress(addr.name);
break;
case "M":
case "m":
addr.name = "CSharp";
this.modifyAddress(out addr);
Console.WriteLine("Name is now {0}.", addr.name);
break;
case "V":
case "v":
this.viewAddresses("Hadi", "Salehy", "C#", "Persian");
break;
case "Q":
case "q":
Console.WriteLine("Bye.");
break;
default:
Console.WriteLine("{0} is not a valid choice", myChoice);
break;
}
}
// وارد کردن یک آدرس
void addAddress(ref Address addr)
{
Console.WriteLine("Name: {0}, Address: {1} added.", addr.name, addr.address);
}
// حذف یک آدرس
void deleteAddress(string name)
{
Console.WriteLine("You wish to delete {0}'s address.", name);
}
// تغییر یک آدرس
void modifyAddress(out Address addr)
{
//Console.WriteLine("Name: {0}.", addr.name); // خطا رخ میدهد
addr = new Address();
addr.name = "Hadi";
addr.address = "C# Persian";
}
// نمایش آدرسها
void viewAddresses(params string[] names)
{
foreach (string name in names)
{
Console.WriteLine("Name: {0}", name);
}
}
}
مثال 2-5، نمونه تغییر یافته مثال 1-5 است که در آن تمامی برنامه ماژولار شده و به متدهای مختلف تقسیم شده است. در زبان C# چهار گونه پارامتر وجود دارند : ref، out، params و value . بمنظور آشنایی با پارامترها، در مثال 2-5 کلاسی با نام Address با دو فیلد از نوع رشته تولید کردهایم.
درون متد Main()، متد getChoice() را فراخوانی کردهایم تا از کاربر ورودی دریافت کنیم و این ورودی در متغیر رشتهای myChoice قرار میگیرد. سپس متغیر myChoice را بعنوان آرگومان به متد makeDecision() ارسال نمودهایم. در اعلان myDecision()، همانطور که ملاحظه مینمایید، پارامتر این متد از نوع رشته و با نام myChoice تعریف شده است. توجه نمایید که این متغیر نیز محلی است و تنها درون متد makeDecision() قابل استفاده است. هرگاه در اعلان متد، برای پارامترهای آن هیچ modifier آورده نشود، این پارامتر بعنوان value در نظر گرفته میشود. در مورد پارامترهای مقداری (value parameter) ، اصل مقدار متغیر یا پارامتر به پشته (Stack) کپی میشود. متغیرهایی که بصورت مقداری بعنوان پارامتر برای یک متد ارسال میشوند، همگی محلی بوده و تغییرات ایجاد شده بر روی آنها به هیچ وجه تغییری بر روی متغیر اصلی ایجاد نمینماید.
دستور switch در متد makeDecision() برای هر case یک متد را فراخوانی مینماید. فراخوانی این متدها با آنچه در متد Main() دید مقداری متفاوت است. علاوه بر مرجع mp، در این فراخوانیها از کلمه کلیدی this نیز استفاده شده است. کلمه کلیدی this ارجاعی به شیء فعلی دارد.
متد addAddress() پارامتری از نوع ref دارد. وجود چنین پارامتری بدین معناست که مرجعی از این پارامتر به متد ارسال میشود و این مرجع همچنان به شیء اصلی درون heap نیز اشاره دارد چراکه آدرس شیء مورد نظر به متد کپی میشود. در مورد پارامترهای ref، هرگونه تغییری که بر روی متغیر محلی رخ دهد، همان تغییر بر روی متغیر اصلی نیز اعمال میگردد. امکان تغییر مرجع وجود ندارد و تنها شیای که مورد آدرسدهی واقع شده، میتواند تغییر پیدا نماید. پارامترهای مرجعی (ref) را میتوان به عنوان عناصر ورودی/خروجی برای متد در نظر گرفت.
پارامترهای out در مواردی استفاده میشوند که ارسال اطلاعات به متد از طریق پارامتر مد نظر نباشد، بلکه ارسال اطلاعات از متد مورد نظر باشد. استفاده از این پارامترها از اینرو کارآمد هستند که برنامه مجبور به کپی کردن پارامتر به متد نیست و از حجم سرباره (Overhead) برنامه میکاهد. در برنامههای عادی این مسئله چندان به چشم نمیآید، اما در برنامههای تحت شبکه که سرعت ارتباط و انتقال دادهها بسیار مهم است، این پارامترها ضروری میشوند.
متد modifyAddress() دارای پارامتری از نوع out است. پارامترهای out فقط به متد فراخواننده آن بازگشت داده میشوند. از آنجائیکه این پارامترها از متد فراخواننده هیچ مقداری دریافت نمیکنند و فقط درون متدی که به عنوان پارامتر به آن ارسال شدهاند قابلیت تغییر دارند، از اینرو درون این متدهایی که به آنها ارسال میشوند، قبل از اینکه بتوان از آنها استفاده نمود باید مقداری به آنها تخصیص داده شود. اولین خط در متد modifyAddress() بصورت توضیحات نوشته شده است. این خط را از حالت توضیحات خارج کرده و سپس برنامه اجرا کنید تا ببینید چه اتفاقی رخ خواهد داد. هنگامیکه این پارامتر مقدار دهی شود و مقداری را به متد فراخواننده خود بازگرداند، این مقدار بر روی متغیر متد فراخواننده کپی میگردد. توجه نمایید که پارامترهای out میبایست قبل از دستور return درون متد مقدار دهی شده باشند.
یکی از ویژگیهای مفید زبان C#، وجود پارامترهای params است که بوسیله آنها میتوان متدی را اعلان کرد که تعداد متغیری متغیر را به عنوان پارامتر دریافت نماید. پارامترهای params حتماً باید یکی از انواع آرایه تک بعدی و یا آرایه دندانهدار (Jagged Array) باشند. در متد makeDecision() چهار متغیر رشتهای را به متد viewAddresses() ارسال نمودهایم که این متد پارامترهای خود را بصورت params دریافت مینماید. همانطور که ملاحظه مینمایید، تعداد متغیرهای ارسالی به متد میتواند متغیر باشد اما دقت داشته باشید که تمامی این متغیرها در یک آرایه تک بعدی قرار گرفتهاند. درون متد viewAddresses() نیز با استفاده از دستور foreach تمامی عناصر موجود در این آرایه را نمایش دادهایم. پارامترهای params فقط متغیرهای ورودی دریافت مینمایند و تغییرات اعمال شده تنها بر روی متغیر محلی تاثیر میگذارد.
خلاصه
در این درس، با ساختار کلی یک متد آشنا شدید. فرا گرفتید که در زبان C# چهار نوع پارامتر برای متدها وجود دارند. پارامترهای value، ref، out و params . همانطور که گفته شد حالت پیش فرض برای پارامترها، value است مگر آنکه صریحاً مشخص گردد.
درس چهارم – دستورالعملهای کنترلی، حلقهها
در این درس نحوه استفاده از دستورالعملهای کنترل حلقه در زبان C# را فرا خواهید گرفت. هدف این درس فهم و درک موارد زیر میباشد :
ü حلقه while
ü حلقه do-while
ü حلقه for
ü حلقه foreach
ü مطالب تکمیلی درباره دستورالعمل break
ü فراگیری نحوه بکارگیری دستورالعمل continue
در درس قبل، نحوه ایجاد یک حلقه بسیار ساده را با استفاده از دستور goto را فرا گرفتید. در همان مطلب نیز اشاره کردیم که این روش، روش مناسبی جهت ایجاد حلقه نیست. در این درس با نحوه صحیح ایجاد حلقهها در زبان C# آشنا خواهید شد. اولین دستوری که با آن آشنا میشوید نیز دستور while است.
حلقه while
ابتدا به مثال زیر توجه نمایید.
using System;
class WhileLoop
{
public static void Main()
{
int myInt = 0;
while (myInt < 10)
{
Console.Write("{0} ", myInt);
myInt++;
}
Console.WriteLine();
}
}
مثال 1-4 که در بالا ملاحظه میکنید، یک حلقه while ساده را نشان میدهد. این حلقه با کلمه کلیدی while آغاز شده و سپس به دنبال آن یک عبارت منطقی قرار میگیرد و مورد بررسی قرار میگیرد. تمامی دستورالعملهای کنترلی از یک عبارت منطقی بهره میگیرند و این بدین معناست که ابتدا این عبارت باید بررسی شود تا مشخص شود مقدار این عبارت true است یا false. در این مثال مقدار متغیر myInt مورد بررسی قرار میگیرد تا چک شود آیا مقدارش از 10 کوچکتر هست یا خیر. چون در ابتدای برنامه به این متغیر مقدار صفر تخصیص داده شده است، عبارت منطقی مقدار true را باز میگرداند و سپس بلوک قرار گرفته بعد از عبارت منطقی مورد اجرا قرار میگیرد.
درون بلوک while ابتدا مقدار متغیر myInt در کنسول نمایش داده میشود و سپس یک واحد به مقدار این متغیر افزوده میگردد. پس از اتمام بلوک while، عبارت منطقی مجددا کنترل میشود و در صورتیکه این عبارت مقدار true بازگرداند، حلقه while مجدداً اجرا میشود. زمانیکه عبارت منطقی مقدار false برگرداند، اجرا برنامه به اولین دستور بعد از بلوک while منتقل میشود. در این مثال اعداد صفر تا 9 بر روی صفحه نمایش داده میشوند و سپس یک خط خالی چاپ شده و اجرای برنامه خاتمه مییابد.
حلقه بعدی که بسیار شبیه به حلقه while میباشد، حلقه do-while است.
حلقه do-while
ابتدا به مثال 2-4 توجه نمایید.
using System;
class DoLoop
{
public static void Main()
{
string myChoice;
do
{
// منویی نمایش داده میشود
Console.WriteLine("My Address Book ");
Console.WriteLine("A - Add New Address");
Console.WriteLine("D - Delete Address");
Console.WriteLine("M - Modify Address");
Console.WriteLine("V - View Addresses");
Console.WriteLine("Q - Quit ");
Console.WriteLine("Choice (A,D,M,V,or Q): ");
// ورودی کاربر بررسی میشود
myChoice = Console.ReadLine();
// تصمیمی بر اساس ورودی کاربر گرفته میشود
switch(myChoice)
{
case "A":
case "a":
Console.WriteLine("You wish to add an address.");
break;
case "D":
case "d":
Console.WriteLine("You wish to delete an address.");
break;
case "M":
case "m":
Console.WriteLine("You wish to modify an address.");
break;
case "V":
case "v":
Console.WriteLine("You wish to view the address list.");
break;
case "Q":
case "q":
Console.WriteLine("Bye.");
break;
default:
Console.WriteLine("{0} is not a valid choice", myChoice);
break;
}
Console.Write("Press Enter key to continue...");
Console.ReadLine();
Console.WriteLine();
} while (myChoice != "Q" && myChoice != "q");
}
}
مثال 2-4 نحوه استفاده از حلقه do-while را نشان میدهد. ساختار نوشتاری این دستور بصورت زیر است :
do
{
دستورالعملهای مورد استفاده در بلوک این دستور، هر دستورالعمل معتبر زبان C# میتواند باشد. عبارت منطقی نیز همانند نمونههائیست که تا کنون با آنها آشنا شدیم و یکی از دو مقدار true یا false را بر میگرداند.
یکی از مصارف عمده حلقه do به جای حلقه while، مواردی است که میخواهیم یکسری دستورالعمل خاص، که آنها را درون بلوک do قرار میدهیم، حداقل یکبار اجرا شوند. در این مثال ابتدا یک منو برای کاربر نمایش داده میشود و سپس ورودی از وی دریافت میگردد. چون حلقه while عبارت منطقی خود در ابتدای اجرای حلقه بررسی مینماید، از اینرو تضمینی برای اجرای دستورات درون بلوک وجود نخواهد داشت، مگر شما بطور صریح برنامه را طوری طراحی نمایید که این عمل اتفاق بیفتد.
یک نگاه کلی به مثال 2-4 بیندازیم. در متد Main() متغیر myChoice را از نوع رشتهای تعریف نمودهایم. سپس یکسری دستورات را بر روی کنسول چاپ نمودهایم. این دستورات منوهای انتخاب برای کاربر هستند. ما باید ورودی از کاربر دریافت کنیم که چون این عمل از طریق Console.ReadLine() صورت گرفته، باید در متغیری از نوع رشتهای قرار گیرد و از اینرو این ورودی را در myChoice قرار دادهایم. ما باید ورودی را از کاربر دریافت کنیم و بر روی آن پردازش انجام دهیم. یک روش کارآمد برای این منظور استفاده از دستورالعمل switch است. همانطور که در دستور switch ملاحظه میکنید، بری default نیز دستوری در نظر گرفته شده است که نشان میدهد مقدار ورودی معتبر نیست.
حلقه for
به مثال 3-4 توجه کنید.
using System;
class ForLoop
{
public static void Main()
{
for (int i=0; i < 20; i++)
{
if (i == 10)
break;
if (i % 2 == 0)
continue;
Console.Write("{0} ", i);
}
Console.WriteLine();
}
}
مثال 3-4 یک حلقه for را نشان میدهد. استفاده از حلقه for برای زمانی مناسب است که دقیقاً بدانید که حلقه چندبار باید تکرا شود. محتویات درون پرانتزهای حلقه for از سه قسمت تشکیل شده است :
(
initializer list لیستی از عبارات است که بوسیله کاما از یکدیگر جدا میشوند. این عبارات تنها یکبار در طول دوره کاری حلقه for پردازش میشوند. همانطور که در مثال 3-4 نیز ملاحظه میکنید، این قسمت معمولا برای تعیین متغیری عددی جهت آغاز عمل شمارش مورد استفاده قرار میگیرد.
پس از اینکه عبارتهای دورن initializer list پردازش شد، حلقه for به سراغ قسمت بعدی، یعنی عبارات منطقی(boolean expression) میرود. در این قسمت تنها یک عبارت منطقی میتوان قرار داد ولی هر اندازه که بخواهید میتوانید این عبارت منطقی را پیچیده نمایید، فقط توجه نمایید که این عبارت باید بگونهای شود که مقدار true یا false برگرداند. از این عبارت منطقی معمولا جهت کنترل متغیر شمارشی استفاده میشود.
هنگامیکه عبارت منطقی مقدار true بازگرداند، دستورالعملهای بلوک for اجرا میشوند. در مثال 3-4 ما از دو دستور if درون حلقه for نیز استفاده کردهایم. اولین دستور if بررسی میکند که آیا مقدار متغیر i برابر با 10 هست یا نه. در اینجا یک نمونه دیگر از استفاده دستور break را ملاحظه میکنید. عملکرد دستور break در اینجا نیز همانند مورد استفاده آن در دستور switch است. در صورت اجرای دستور break اجرای حلقه for خاتمه یافته و اجرای برنامه به اولین دستور بعد از حلقه for منتقل میشود.
دومین دستور if با اسقتاده از عملگر باقیمانده (%) بررسی میکند که آیا متغیر i بر 2 بخش پذیر هست یا نه. در صورتیکه متغیر i بر 2 بخش پذیر باشد، دستور continue اجرا میشود. پس از اجرای دستور continue از سایر دستورات حلقه for که بعد از continue قرار گرفتهاند صرفهنظر میشود و اجرای برنامه به اول حلقه for باز میگردد.
قسمت سوم در حلقه for، قسمت postloopaction list است. پس از اینکه تمامی دستورات درون حلقه for اجرا شد، اجرای حلقه به این قسمت باز میگردد. این قسمت لیستی از عملیاتی است که میخواهید پس از اجرای دستورات درون بلوک حلقه for انجام شوند. در مثال 3-4 این عمل، اضافه کردن یک واحد به متغیر شمارشی است. پس از افزوده شدن یک واحد به متغیر شمارشی، عبارت منطقی مجدداً مورد بررسی قرار میگیرد و در صورتیکه مقدار این عبارت برابر با true باشد، حلقه for مجدداً اجرا میگردد. حلقه for تا زمانیکه عبارت منطقی برابر با true باشد اجرا میشود.
حلقه foreach
به مثال 4-4 توجه کنید.
using System;
class ForEachLoop
{
public static void Main()
{
string[] names = {"Hadi", "Salehy", "C#", "Persian"};
foreach (string person in names)
{
Console.WriteLine("{0} ", person);
}
}
}
حلقه foreach برای پیمایش مجموعهها بسیار مناسب است. یک نمونه از مجموعهها در C#، آرایهها هستند که در مثال 4-4 نیز مورد استفاده قرار گرفته است. اولین کاری که در متد Main() صورت گرفته، اعلان آرایه names از نوع رشتهای و مقدار دهی آن است.
درون پرانتزهای foreach عبارتی متشکل از دو المان قرار دارد که این المانها بوسیله کلمه کلیدی in از یکدیگر جدا شدهاند. المان سمت راست، مجموعهایست که میخواهید اعضای آنرا مورد پیمایش قرار دهید. المان سمت چپ، متغیری از نوع مجموعه مورد نظر است که مقادیر پیمایش شده را بر میگرداند.
در هربار پیمایش، عنصری جدیدی از مجموعه درخواست میشود. این درخواستها از طریق متغیر فقط خواندنی تعریف شده درون پرانتزهای foreach بازگردانده میشوند. تا زمانیکه عنصری در مجموعه وجود داشته باشد که مورد پیمایش قرار نگرفته است، حلقه foreach به کار خود ادامه خواهد داد زیرا عبارت منطقی حلقه foreach مقدار true را باز میگرداند. به محض اینکه تمامی عناصر مجموعه پیمایش شد، عبارت منطقی برابر با false شده و اجرای حلقه foreach خاتمه مییابد. در این حالت اجرای برنامه به اولین دستور بعد از حلقه foreach منتقل میگردد.
در مثال 4-4، متغیری از نوع رشته با نام person برای نگهداری عناصری که از آرایه names خوانده میشود، تعریف کردهایم. تا زمانیکه اسمی در آرایه names وجود داشته باشد، در متغیر person قرار میگیرد و درون حلقه foreach بوسیله دستور Console.WriteLine() در خروجی نمایش داده میشود.
نکته : یکی از مهمترین ویژگیهای حلقه foreach در آنست که فقط میتواند عناصر یک مجموعه را بخواند و نمیتواند تغییری در آنها ایجاد نماید. مزیت دیگر آن، پیمایش تمامی عناصر مجموعه بدون اطلاع از تعداد عناصر موجود در آن است.
خلاصه
در این درس با نحوه کار با دستورالعملهای for، while، do-while و foreach آشنا شدید. حلقه while تا زمانیکه شرطش صحیح باشد انجام میشود بدین معنی که تا زمانیکه عبارت منطقی آن دارای مقدار true باشد، دستورات درون بلوک آن اجرا میشوند. حلقه do، دستورات بلوک خود را حداقل یکبار اجرا میکند و سپس شرط مورد نظر را بررسی مینماید و در صورتیکه عبارت منطقی آن مقدار true بازگرداند، دستورات بلوک خود را تکرار مینماید. حلقه for دستورات بلوک خود را به تعداد دفعات مشخص اجرا مینماید و حلقه foreach عناصر یک مجموعه را مورد پیمایش قرار میدهد. در نهایت نیز اشاره میشود که روند اجرای حلقهها با استفاده از دستورات break و continue تغییر مینماید.
درس سوم – دستورالعملهای کنترلی و شرطی
در این درس با دستورالعملهای کنترل و انتخاب در C# آشنا میشوید. هدف این درس عبارتست از :
بررسی دستور if و انواع مختلف آن
در درسهای گذشته، برنامههایی که مشاهده میکردید از چندین خط دستور تشکیل شده بودند که یکی پس از دیگری اجرا میشدند و سپس برنامه خاتمه مییافت. در این برنامهها هیچ عمل تصمیمگیری صورت نمیگرفت و تنها دستورات برنامه به ترتیب اجرا میشدند. مطالب این درس نحوه تصمیمگیری در یک برنامه را به شما نشان میدهد.
اولین دستور تصمیمگیری که ما آنرا بررسی مینماییم، دستورالعمل if است. این دستور دارای سه فرم کلی : تصمیمگیری ساده، تصمیمگیری دوگانه، تصمیمگیری چندگانه میباشد.
مثال 1-3 – فرمهای دستورالعمل if
using System;
class IfSelect
{
public static void Main()
{
string myInput;
int myInt;
Console.Write("Please enter a number: ");
myInput = Console.ReadLine();
myInt = Int32.Parse(myInput);
//تصمیمگیری ساده و اجرای عمل داخل دو کروشه
if (myInt > 0)
{
Console.WriteLine("Your number {0} is greater than zero.", myInt);
}
//تصمیمگیری ساده و اجرای عمل بدون استفاده از دو کروشه
if (myInt < 0)
Console.WriteLine("Your number {0} is less than zero.", myInt);
// تصمیمگیری دوگانه
if (myInt != 0)
{
Console.WriteLine("Your number {0} is not equal to zero.", myInt);
}
else
{
Console.WriteLine("Your number {0} is equal to zero.", myInt);
}
// تصمیمگیری چندگانه
if (myInt < 0 || myInt == 0)
{
Console.WriteLine("Your number {0} is less than or equal to zero.", myInt);
}
else if (myInt > 0 && myInt <= 10)
{
Console.WriteLine("Your number {0} is between 1 and 10.", myInt);
}
else if (myInt > 10 && myInt <= 20)
{
Console.WriteLine("Your number {0} is between 11 and 20.", myInt);
}
else if (myInt > 20 && myInt <= 30)
{
Console.WriteLine("Your number {0} is between 21 and 30.", myInt);
}
else
{
Console.WriteLine("Your number {0} is greater than 30.", myInt);
}
} //Main()پایان متد
} //IfSelectپایان کلاس
برنامه 1-3 از یک متغیر myInt برای دریافت ورودی از کاربر استفاده مینماید، سپس با استفاده از یک سری دستورات کنترلی، که همان دستور if در اینجاست، عملیات خاصی را بسته به نوع ورودی انجام میدهد. در ابتدای این برنامه عبارت Please enter a umber: در خروجی چاپ میشود. دستور Console.ReadLine() منتظر میماند تا کاربر ورودی وارد کرده و سپس کلید Enter را فشار دهد. همانطور که در قبل نیز اشاره کردهایم، دستور Console.ReadLine() عبارت ورودی را به فرم رشته دریافت مینماید پس مقدار ورودی کاربر در اینجا که یک عدد است به فرم رشتهای در متغیر myInput که از نوع رشتهای تعریف شده است قرار میگیرد. اما میدانیم که برای اجرای محاسبات و یا تصمیمگیری بر روی اعداد نمیتوان از آنها در فرم رشتهای استفاده کرد و باید آنها را بصورت عددی مورد استفاده قرار داد. به همین منظور باید متغیر myInput را به نحوی به مقدار عددی تبدیل نماییم. برای این منظور از عبارت Int32.Parse() استفاده مینماییم. این دستور مقدار رشتهای متغیر داخل پرانتزش را به مقدار عددی تبدیل کرده و آنرا به متغیر دیگری از نوع عددی تخصیص میدهد. در این مثال نیز همانطور که دیده میشود، myInput که تز نوع رشتهای است در داخل پرانتز قرار گرفته و این مقدار برابر با myInt که از نوع int است قرار گرفته است. با این کار مقدار عددی رشته ورودی کاربر به متغیر myInt تخصیص داده میشود. (توضیح کاملتری در مورد Int32 و سایر تبدیلات مشابه به آن در درسهای آینده و در قسمت نوعهای پیشرفته مورد بررسی قرار میگیرند.)حال ما متغیری از نوع مورد نظر در دست داریم و میتوانیم با استفاده از دستور if بر روی آن پردازش انجام داده و تصمیمگیری نماییم.
دستور if
اولین دستور بصورت if (boolean expression) {statements} آورده شده است. دستور if با استفاده از کلمه کلیدی if آغاز میشود. سپس یک عبارت منطقی درون یک زوج پرانتز قرار میگیرد . پس از بررسی این عبارات منطقی دستورالعمل/دستورالعملهای داخل کروشه اجرا میشوند. همانطور که مشاهده مینمایید، دستور if یک عبارت منطقی را بررسی میکند. در صورتیکه مقدار این عبارات true باشد دستورهای داخل بلوک خود را اجرا مینماید(قبلا توضیح داده شد که دستورهایی که داخل یک زوج کروشه {} قرار میگیرند در اصطلاح یک بلوک نامیده میشوند.) و در صورتیکه مقدار آن برابر با false باشد اجرای برنامه به بعد از بلوک if منتقل میشود. در این مثال همانطور که ملاحظه مینمایید، عبارت منطقی دستور if بشکل if(myInt > 0) است. در صورتیکه مقدار myInt بزرگتر از عدد صفر باشد، دستور داخل بلوک if اجرا میشود و در غیر اینصورت اجرای برنامه به بعد از بلوک if منتقل میگردد.
دومین دستور if دراین برنامه بسیار شبیه به دستور اول است، با این تفاوت که در این دستور، دستور اجرایی if درون یک بلوک قرار نگرفته است. در صورتیکه بخواهیم با استفاده از دستور if تنها یک دستورالعمل اجرا شود، نیازی به استفاده از بلوک برای آن دستورالعمل نمیباشد. استفاده از بلوک تنها زمانی ضروری است که بخواهیم از چندین دستور استفاده نماییم.
دستور if-else
در بیشتر موارد از تصمیمگیریهای دوگانه یا چندگانه استفاده میشود. در این نوع تصمیمگیریها، دو یا چند شرط مختلف بررسی میشوند و در صورت true بودن یکی از آنها عمل مربوط به آن اجرا میگردد. سومین دستور if در این برنامه نشان دهنده یک تصمیمگیری دوگانه است. در این حالت درصورتیکه عبارت منطقی دستور if برابر با true باشد دستور بعد از if اجرا میشود و در غیر اینصورت دستور بعد از else به اجرا در میآید. در حقیقت در این حالت میگوئیم " اگر شرط if صحیح است دستورات مربوط به if را انجام بده و درغیر اینصورت دستورات else را اجرا کن".
فرم کلی دستور if-else بصورت زیر است :
if (boolean expression)
{statements}
else
{statements}
که در آن boolean expression عبارت منطقی است که صحت آن مورد بررسی قرار میگیرد و statements دستور یا دستوراتی است که اجرا میگردند.
دستور if-else if … else یا if تودرتو
در صورتیکه نیاز باشد تا چندین حالت منطقی مورد بررسی قرار گیرد و دستورات مربوط به یکی از آنها اجرا شود، از فرم تصمیمگیری چندگانه استفاده مینماییم. این نوع استفاده از دستور if در اصطلاح به if تودرتو (Nested If) معروف است چراکه در آن از چندین دستور if مرتبط به یکدیگر استفاده شده است. چهارمین دستور if در مثال 1-3 استفاده از if تودرتو را نشان میدهد. در این حالت نیز دستور با کلمه کلیدی if آغاز میگردد. شرطی بررسی شده و در صورت true بودن دستورات مربوط به آن اجرا میگردد. اما اگر مقدار این عبارت منطقی false بود آنگاه شرطهای فرعی دیگری بررسی میشوند.این شرطهای فرعی با استفاده از else if مورد بررسی قرار میگیرند. هر یک از این شرطها دارای عبارات منطقی مربوط به خود هستند که در صورت true بودن عبارت منطقی دستورات مربوط به آنها اجرا میگردد و در غیر اینصورت شرط بعدی مورد بررسی قرار میگیرد. باید توجه کنید که در ساختار if تودرتو تنها یکی از حالتها اتفاق میافتد و تنها یکی از شرطها مقدار true را بازمیگرداند.
فرم کلی if تودرتو بشکل زیر است :
if (boolean expression)
{statements}
else if (boolean expression)
{statements}
…
else
{statements}
عملگرهای OR و AND (|| و &&)
نکته دیگری که باید در اینجا بدان اشاره کرد، نوع شرطی است که در عبارت منطقی دستور if آخر مورد استفاده قرار گرفته است. در این عبارت منطقی از عملگر || استفاده شده است که بیانگر OR منطقی است. عملگر OR زمانی مقدار true بازمیگرداند که حداقل یکی از عملوندهای آن دارای مقدار true باشد. بعنوان مثال در عبارت (myInt < 0 || myInt == 0)، در صورتیکه مقدار متغیر myInt کوچکتر یا مساوی با صفر باشد، مقدار عبارت برابر با true است. نکته قابل توجه آنست که در زبان C#، همانطور که در درس دوم به آن اشاره شد، دو نوع عملگر OR وجود دارد. یکی OR منطقی که با || نمایش داده میشود و دیگری OR معمولی که با | نشان داده میشود. تفاوت بین این دو نوع OR در آنست که OR معمولی هر دو عملگر خود را بررسی مینماید اما OR منطقی تنها در صورتیکه عملگر اول آن مقدار false داشته باشد به بررسی عملگر دوم خود میپردازد.
عبارت منطقی (myInt > 0 && myInt <= 10) حاوی عملگر AND شرطی (&&) میباشد. این عبارت در صورتی مقدار true بازمیگرداند که هر دو عملوند AND دارای مقدار true باشند. یعنی در صورتیکه myInt هم بزرگتر از صفر باشد و هم کوچگتر از 10، مقدار عبارت برابر با true میگردد. در مورد AND نیز همانند OR دو نوع عملگر وجود دارد. یکی AND معمولی (&) و دیگری AND شرطی (&&). تفاوت این دو نیز در آنست که AND معمولی (&) همیشه هر دو عملوند خود را بررسی مینماید ولی AND شرطی (&&) تنها هنگامی به بررسی عملوند دوم خود میپردازد که مقدار اولین عملوندش برابر با true باشد. عملگرهای منطقی (|| و &&) را در اصطلاح عملگرهای میانبر (short-circuit) مینامند چراکه تنها در صورت لزوم عملوند دوم خود را بررسی مینمایند و از اینرو سریعتر اجرا میشوند.
بررسی دستور switch
همانند دستور if، دستور switch نیز امکان تصمیمگیری را در یک برنامه فراهم مینماید.
مثال 2-3 – دستورالعمل switch
using System;
class SwitchSelect
{
public static void Main()
{
string myInput;
int myInt;
begin:
Console.Write("Please enter a number between 1 and 3: ");
myInput = Console.ReadLine();
myInt = Int32.Parse(myInput);
// بهمراه متغیری از نوع صحیح switch دستور
switch (myInt)
{
case 1:
Console.WriteLine("Your number is {0}.", myInt);
break;
case 2:
Console.WriteLine("Your number is {0}.", myInt);
break;
case 3:
Console.WriteLine("Your number is {0}.", myInt);
break;
default:
Console.WriteLine("Your number {0} is not between 1 and 3.", myInt);
break;
} //switchپایان بلوک
decide:
Console.Write("Type "continue" to go on or "quit" to stop: ");
myInput = Console.ReadLine();
// بهمراه متغیری از نوع رشتهای switch دستور
switch (myInput)
{
case "continue":
goto begin;
case "quit":
Console.WriteLine("Bye.");
break;
default:
Console.WriteLine("Your input {0} is incorrect.", myInput);
goto decide;
} //switchپایان بلوک
} //Main()پایان متد
} //SwitchSelectپایان کلاس
مثال 2-3 دو مورد استفاده از دستور switch را نشان میدهد. دستور switch بوسیله کلمه کلیدی switch آغاز شده و به دنبال آن عبارت دستور switch قرار میگیرد. عبارت دستور switch میتواند یکی از انواع زیر باشد : sbyte, byte, short, ushort, int, uint, long, ulong, char, string, enum .(نوع enum در مبحث جداگانهای مورد بررسی قرار خواهد گرفت.) در اولین دستور switch در مثال 2-3، عبارت دستور switch از نوع عددی صحیح (int) میباشد.
به دنبال دستور و عبارت switch، بلوک switch قرار میگیرد که در آن گزینههایی قرار دارند که جهت منطبق بودن با مقدار عبارت switch مورد بررسی قرار میگیرند. هر یک از این گزینهها با استفاده از کلمه کلیدی case مشخص میشوند. پس از کلمه کلیدی case خود گزینه قرار میگیرد و به دنبال آن ":" و سپس دستوری که باید اجرا شود. بعنوان مثال به اولین دستور switch در این برنامه توجه نمایید. در اینجا عبارت دستور switch از نوع int است. هدف از استفاده از دستور switch آنست که از بین گزینههای موجود در بلوک switch، گزینهای را که مقدارش با مقدار عبارت switch برابر است پیدا شده و عمل مرتبط با آن گزینه اجرا شود. در این مثال مقدار متغیر myInt بررسی میشود. سپس اگر این مقدار با یکی از مقادیر گزینههای داخل بلوک switch برابر بود، دستور یا عمل مربوط به آن گزینه اجرا میگردد. توجه نمایید که در این مثال منظور ما از گزینه همان عدد پس از case است و منظور از دستور عبارتی است که پس از ":" قرار گرفته است. بعنوان مثال، در دستور زیر :
case 1:
Console.WriteLine("Your number is {0}.", myInt);
عدد 1، گزینه مورد نظر ما و دستور Console.WriteLine(…)، عمل مورد نظر است. در صورتیکه مقدار myInt برابر با عدد 1 باشد آنگاه دستور مربوط به case 1 اجرا میشود که همان Console.WriteLine("Your number is {0}.", myInt); است. پس از منطبق شدن مقدار عبارت switch با یکی از case ها، بلوک switch باید خاتمه یابد که این عمل بوسیله استفاده از کلمه کلیدی break، اجرای برنامه را به اولین خط بعد از بلوک switch منتقل مینماید.
همانطور که در ساختار دستور switch مشاهده مینمایید، علاوه بر case و break، دستور دیگری نیز در داخل بلوک وجود دارد. این دستور یعنی default، برای زمانی مورد استفاده قرار میگیرد که هیچ یک از گزینههای بلوک switch با عبارت دستور switch منطبق نباشند. به عبارت دیگر درصورتیکه مقدار عبارت switch با هیچ یک از گزینههای case برابر نباشد، دستور مربوط به default اجرا میگردد. استفاده از این دستور در ساختار بلوک switch اختیاری است. همچنین قرار دادن دستور break پس از دستور default نیز اختیاری میباشد.
همانطور که قبلاً نیز گفته شد پس از هر دستور case، به منظور خاتمه دادن اجرای بلوک switch باید از یک break استفاده نمود. دو استثنا برای این موضوع وجود دارد. اول اینکه دو دستور case بدون وجود کد و دستورالعملی در بین آنها، پشت سر هم قرار گیرند و دیگری در زمانیکه از دستور goto استفاده شده باشد.
در صورتیکه دو دستور case بدون وجود کدی در بین آنها، پشت سر یکدیگر قرار گیرند، بدین معناست که برای هر دو case مورد نظر یک عمل خاص در نظر گرفته شده است. به مثال زیر توجه نمایید.
switch (myInt)
{
case 1:
case 2:
case 3:
Console.WriteLine("Your number is {0}.", myInt);
break;
default:
Console.WriteLine("Your number {0} is not between 1 and 3.", myInt);
break;
}
در این مثال، همانطور که مشاهده میکنید، سه دستور case بدون وجود کدی در بین آنها پشت سر یکدیگر قرار گرفتهاند. این عمل بدین معناست که برای تمامی گزینههای 1، 2 و 3 دستور ;(Console.WriteLine("Your number is {0}.", myInt اجرا خواهد شد. یعنی اگر مقدار myInt برابر با هر یک از مقادیر 1، 2 و 3 باشد، یک دستور برای آن اجرا میشود.
نکته قابل توجه دیگر در مورد بلوک switch آنست که، دستورات case حتماً نباید یک دستور باشد بلکه میتوان از یک بلوک دستور برای case استفاده نمود.
دومین استفاده از دستور switch در مثال 2-3، دارای عبارتی از نوع رشتهایست. در این بلوک switch چگونگی استفاده از دستور goto نیز نشان داده شده است. دستور goto اجرای برنامه را به برچسبی (label) که معین شده هدایت مینماید. در حین اجرای این برنامه، اگر کاربر رشته continue وارد نماید، این رشته با یکی از گزینههای دومین switch منطبق میشود. چون دستور case مربوط به این گزینه دارای دستور goto است، اجرای برنامه به برچسبی که این دستور مشخص کرده فرستاده میشود، بدین معنی که اجرای برنامه به ابتدای جایی میرود که عبارت begin: در آنجا قرار دارد (در اوایل متد Main()). بدین صورت اجرای برنامه از بلوک switch خارج شده و به ابتدای برنامه و در جائیکه برچسب begin: قرار گرفته ارسال میشود. در این برنامه، استفاده از چنین حالتی استفاده از goto باعث ایجاد یک حلقه شده است که با وارد کردن عبارت quit اجرای آن به پایان میرسد.
در صورتیکه هیچ یک از عبارات continue و یا quit وارد نشوند، اجرای switch به گزینه default میرود و در این گزینه ابتدا پیغام خطایی بر کنسول چاپ شده و سپس با استفاده از دستور goto پرشی به برچسب decide صورت میگیرد. پس از پرش به برچسب decide، از کاربر پرسیده میشود که آیا میخواهد اجرای برنامه را ادامه دهد یا خیر.( با وارد کردن گزینههای continue یا quit) همانطور که میبینید در اینجا نیز حلقهای تولید شده است.
استفاده از دستور goto در بلوک switch میتواند موثر باشد اما باید توجه نمایید که استفادههای بی مورد از دستور goto باعث ناخوانا شدن برنامه شده و عیبیابی (Debug) برنامه را بسیار دشوار مینماید. در برنامهنویسیهای امروزی استفاده از دستور goto بغیر از موارد بسیار لازم و ضروری منسوخ شده و به هیچ عنوان توصیه نمیشود. برای تولید و ساخت حلقه نیز دستورات مفید و سودمندی در زبان تعبیه شدهاند که استفاده از goto را به حداقل میرسانند. دستورات حلقه در مبحث آینده مورد بررسی قرار خواهند گرفت.
نکته پایانی این مبحث آنست که توجه نمایید که به جای استفاده از دستور switch میتوانید از چندین دستور if-else استفاده نماید. دو قطعه برنامه زیر معادل یکدیگر میباشند.
switch(myChar)
{
case 'A' :
Console.WriteLine("Add operation ");
break;
case 'M' :
Console.WriteLine("Multiple operation ");
break;
case 'S' :
Console.WriteLine("Subtraction operation ");
break;
default :
Console.WriteLine("Error, Unknown operation ");
break;
}
معادل بلوک switch با استفاده از if-else
if (myChar == 'A')
Console.WriteLine("Add operation ");
else if (myChar == 'M')
Console.WriteLine("Multiple operation ");
else if (myChar == 'S')
Console.WriteLine("Subtraction operation ");
else
Console.WriteLine("Error, Unknown operation ");
همانطور که ملاحظه میکنید استفاده از بلوک دستور switch بسیار سادهتر از استفاده از if-else های تودرتو است.
در این درس با نحوه تصمیمگیری در برنامه بوسیله دستور if و switch آشنا شدید. با نحوه عملکرد و استفاده دستور goto نیز آشنایی پیدا کردید. در پایان مجدداً یادآوری میکنم که در استفاده از دستور goto با احتیاط عمل نمایید و به جز در موارد ضروری از آن استفاده نکنید.