آموزش برنامه نويسي با پاسكال فصل سوم
آموزش Turbo Pascal 7.0
زير برنامههاي Procedure و Function
هنگامي
كه حجم دستورات برنامه افزايش مييابد، مديريت آن مشكل ميشود. براي ايجاد
نظم بيشتر در برنامه و تفكيك وظايف و همچنين خواناتر شدن دستورالعملها و
كاهش پيچيدگي آن، بهتر است از زيربرنامهها استفاده نمائيم.
در پاسكال، دو نوع قطعه برنامه به نام زيربرنامة Procedure و زيربرنامة تابع Function
وجود دارد. اين دو نوع از ساختار برنامهها، شبيه به هم هستند ولي چگونگي
دسترسي به آنها متفاوت است و هر يك از زيربرنامهها به روش مختلفي اطلاعات
را رد و بدل ميكنند.
تابع (Function)
تابع، مجموعهاي از يك يا چند دستور است كه تقريباً مستقل بوده و يك نتيجه يا مقدار بازگشتي (Return Value) را برميگرداند. نحوه كاربرد تابع مانند عبارات (اعم از محاسباتي و غيره) ميباشد. هر تابع فقط و فقط يك مقدار را برميگرداند، نه بيشتر و نه كمتر. براي آنكه يك تابع بتواند در حالات مختلف، وظيفة خود را به درستي انجام دهد و مجبور به بازنويسي آنها نباشيم، از پارامترهاي ورودي (آرگومانها) استفاده مينمائيم.
انواع تابع :
1ـ تابع استاندارد
2ـ توابع تعريفي
تعريف تابع استاندارد : توابعي هستند كه در محيط كامپايلر تعريف شدهاند و نيازي به تعريف مجدد آنها نيست. مانند : توابع رياضي ــ Sqrt ، ABS و غيره و توابع كاراكتري مانند: ORD ، CHR و غيره.
توابع تعريفي : توابعي هستند كه براي يك منظور خاص طراحي ميشوند و توسط برنامهنويس نوشته ميشوند.
شكل كلي :
Function نوع مقدار بازگشتي : (نوع : پارامتر ورودي) شناسه يا نام تابع ;
تعاريف داخلي
(دادهها ، ثابتها ، متغيرها)
Begin
دستورات
.
.
.
عبارت يا نتيجه بازگشتي =: نام تابع ;
End ;
نكته : توابع نيز مانند Procedure كه بعداً توضيح ميدهيم بين Var و Begin اصلي برنامه تعريف ميشوند.
مثال : برنامهاي
بنويسيد كه با استفاده از تابع، فاكتوريل اعداد طبيعي دلخواه را محاسبه و
چاپ نمايد و اين كار آنقدر ادامه يابد تا وقتي كه عدد ورودي، منفي باشد.
حل: توجه نمائيد كه فاكتوريل عدد طبيعي مانندn برابر است با حاصل ضرب اعداد طبيعي از يك تا n .
به عنوان مثال :
n! = 1 * 2 * … * n (n فاكتوريل)
5! = 1 * 2 * 3 * 4 * 5 = 120 (پنج فاكتوريل)
0! = 1
1! = 1
Function Fact (n: integer) : longint ;
Var
i : integer ;
f : longint ;
Begin
f := 1 ;
If (n => 0) then
For i := 1 to n DO
f := f * i ;
fact := f ;
End ;
{ …………………….. }
Var
x : integer ;
{……. بلوك اصلي برنامه ……. }
Begin
While ( True ) do
Begin
Write (‘ Enter number : ‘) ;
Readln (x) ;
If (x<0) then Break ; {خروج از حلقه}
Writeln (‘Factoriel = ‘ , Fact(x) ) ;
End ;
End.
در مثال فوق، نوع مقدار بازگشتي تابع را، Longint در نظر گرفتهايم. زيرا ممكن است حاصل ضرب اعداد طبيعي براي محاسبة فاكتوريل، عدد بزرگي شده و در Integer نگنجد.
نكته : نوع
نتيجه بازگشتي تابع حتماً بايد از انواع ساده يا استاندارد يا اسكالر
باشد. به عنوان مثال نميتوان از نوع ركورد يا آرايه باشد. همچنين در حالت
خاص، يك تابع ميتواند پارامتر ورودي نداشته باشد كه در اين صورت در تعريف
تابع، قسمت مربوط به جفت پرانتزها را نمينويسيم.
مثال :
Function Sample: integer ;
Begin
Sample := 20 ;
End ;
Begin
Writeln (‘ ………………’) ;
Writeln (Sample) ;
End.
در اين حالت خاص، تابع همانند يك مقدار ثابت (Const) عمل نموده است.
نكته : پارامتر
ورودي تابع (يا پردازه)، يك اسم يا نماد فرضي است كه در هنگام فراخواني
تابع ممكن است مقادير يا متغيرهايي جايگزين آن گردد كه همنام با نام
پارامتر نباشد. به عنوان نمونه، در تابع fact ؛ پارامتر تابع فاكتوريل؛ n بود ولي در هنگام فراخواني تابع در برنامه، متغير x را جايگزين آن نموديم.
تابع
(يا پردازه) ميتواند بيش از يك پارامتر ورودي داشته باشد. اگر پارامترها
همنوع باشند ميتوانيم نام آنها را در كنار هم نوشته و با علامت كاما جدا
نمائيم. مثلاً :
Function model(x,y,z : integer) : Real ;
اگر پارامتر همنوع نباشد، هر پارامتر و نوع مربوطه را جداگانه نوشته و آنها را با علامت سميكالون از هم جدا مينمائيم. مثلاً :
Function Sample(x: char ; y,z: Real) : Real ;
نكته : به هنگام فراخواني تابع (دو پردازه) بايد تعداد، ترتيب و نوع پارامترها با تعريف تابع (و پردازه) سازگار باشد.
زير برنامة Procedure :
هدف : جلوگيري از پيچيدگي برنامهها و تبديل برنامههاي بزرگ به برنامههاي كوچكتر است زيرا Procedure مجموعهاي از يك يا چند دستورالعمل ميباشد كه كار يا عملي را براي ما انجام ميدهد.
شكل كلي :
Procedure ( نوع : پارامتر) نام پردازه ;
.
.
Const
ثابتها
Type
دادهها
Var
متغيرها
Begin
دستورات
End ;
نكته : يك پــــردازه، ممكن است فـاقد پارامتر ورودي باشد. در برخي موارد پردازه را “روال” يا “زير روال” نيز ناميدهاند.
Procedure Print_Sum(x,y : integer) ;
Begin
Writeln(‘………………’) ;
Writeln(‘Sum = ‘ , x + y) ;
Writeln(‘………………’) ;
End ;
Var
a,b : integer ;
Begin
Write(‘Enter 2 numbers : ‘) ;
Readln (a,b) ;
Print Sum (a,b) ;
End.
نكته : قسمت تعاريف داخلي در پردازه (و تابع) اختياري است. مثلاً در همين برنامة بالا، پردازه Print_Sum
، فاقد بخش متغيرها بود. همچنين بايد توجه داشته باشيم كه ترتيب، تعداد و
نوع پارامترها در هنگام فراخواني، با آنچه كه در تعريف پردازه (يا تابع)
آمده است، بايد سازگار باشد.
متغيرهاي سراسري (Global) و محلي (Local) :
تا كنون دريافتهايد كه هر تاع يا پردازه ميتواند داراي بخش تعاريف داخلي (متغيرها و …) باشد. متغيرهايي كه درون توابع و پردازهها تعريف ميشوند، در اصطلاح، “محلي” (Local)
هستند. اين متغيرها فقط در محدودة همان تابع و پردازه، معتبر و قابل
استفاده خواهند بود. متغيرهاي محلي در زمان فراخواني تابع يا پردازه به
صورت موقتي در حافظه ايجاد ميشوند و پس از اتمام دستورات و رجوع به محل
فراخواني، از بين ميروند. پس يكي از مزاياي استفاده از توابع و
پردازهها، كاستن از حجم متغيرهاي سراسري (دائمي) و به دنبال آن صرفهجويي
در حافظه مصرفي ميباشد. متغيرهاي محلي را ميتوانيم به كاغذ چركنويس
تشبيه نمائيم كه پس از انجام محاسات و به دست آوردن نتيجه نهايي و
پاكنويس نمودن، آن را در سطل زباله مياندازيم.
متغيرهايي كه در خارج از توابع و پردازهها (درقسمت تعاريف برنامه اصلي) مشخص ميشوند، متغيرهاي سراسري (Global)
ناميده ميشوند. اين متغيرها در سراسر برنامه (اعم از بلوك اصلي يا درون
بلوك تابع و پردازههاي پايينتر) قابل استفاده و معتبر هستند. نكته مهمي
كه بايد به خاطر بسپاريد آن است كه هر گونه تغيير و دستكاري در متغيرهاي
سراسري در هر قسمت از برنامه در ساير قسمتها تأثير خواهد گذاشت.
مثال :
Var
x,y : integer ; {متغيرهاي سراسري}
Function Power 3 (k: integer) : integer ;
Begin
y := y – 5 ;
Power := k * k * k ;
End ;
{……… بلوك اصلي ………}
Begin
y := 20 ;
Write (‘Enter number : ‘) ;
Readln (x) ;
Writeln (Power 3(x) ) ;
Writeln (y) ;
End.
توجه : چنان
چه در يك برنامه، يك يا چند متغير هم به صورت سراسري در بلوك اصلي تعريف
گردد و هم درون تابع و پردازهها معرفي شوند، هيچگونه اشكالي پديد
نميآيد. فقط بايد توجه داشت كه هر كدام در محدوده خود عمل مينمايند.
مثال :
Var
x : integer ; {متغير سراسري}
Procedure a;
Begin
Writeln(x) ; {متغير سراسري}
End ;
Procedure b;
Var
x : real ; {متغير محلي}
Begin
x := 12.5 ; {متغير محلي}
Writeln (x) ; {متغير محلي}
End ;
{……… بلوك اصلي ………}
Begin
x := 7 ; {متغير سراسري}
b ;
a ;
End.
پس از اجراي اين برنامه، ابتدا پردازة b اجرا ميشود و عدد 12.5 را چاپ ميكند. در اين پردازه به دليل آنكه متغير x مجدداً تعريف شده است، به صورت محلي در نظر گرفته ميشود. سپس پردازة a اجرا ميگردد و عدد 7 را چاپ مينمايد. در اين پردازه چون متغير x به صورت محلي تعريف نشده است، از متغير سراسري x استفاده ميگردد.
پارامتر ظاهري (Formal Parameter) و پارامتر واقعي (Actual Parameter)
به
هنگام تعريف و شناسايي يك تابع يا پردازه، تمامي پارامترهاي آن، ظاهري
هستند اما به هنگام فراخواني همان تابع يا پردازه با مادير جايگزين شده در
پارامترها از پارامترهاي واقعي استفاده مينمائيم.
نكته : به
هنگام فراخواني تابع يا پردازه، هرگونه تغيير درون آن بر روي پارامترهاي
ظاهري مقداري تأثير بر پارامترهاي واقعي نخواهد داشت. به مثال زير توجه
كنيد :
Procedure Sample(x: integer) ;
Begin
x := x + 10 ; {پارامتر ظاهري}
Writeln (x) ;
End ;
{…… بلوك اصلي ……}
Var
a : integer ;
Begin
a := 14 ;
sample (a) ; {پارامتر واقعي}
Writeln (a) ;
End.
توجه : مطالبي
كه در مورد متغيرهاي سراسري و محلي بحث شد، در مورد ثابتها و انواع
دادهها نيز معتبر ميباشد. مطالب ارائه شده در مورد پارامترهاي ظاهري و
واقعي نيز در توابع و پردازهها، مشابه هم است.
پارامتر مقداري (Value Parameter) و پارامتر متغير (Variable Parameter)
در
حالت عادي، به هنگام تعريف پردازهها و توابع، پارامتهاي به كار رفته از
نوع مقداري هستند. بدين معني كه يه هنگام فراخواني و استفاده از
پارامترهاي واقعي، هم ميتوان متغير به كار ببريم و مقدار ثابت.
به مثال صفحه بعد توجه نمائيد :
Procedure Sample (n: integer) ;
Begin
n := n * 2 ;
Writeln (n) ;
End ;
Var
x : Real ;
Begin
x := 18.5 ;
Sample (x) ; {فراخواني با متغير}
Writeln (‘…………………….’) ;
Sample (10.25) ; {فراخواني با مقدار ثابت}
Writeln (‘……………………’) ;
Writeln (x) ;
End.
نكته 1 : تعريف پارامترهاي متغير به پردازه اختصاص ندارد و در ليست پارامترهاي تابع نيز ميتوان پارامتر متغير تعريف كرد.
مثال :
Function f(Var x: byte ; i: integer) : byte ;
Begin
x := i * 2 ;
i := Sqrt (x) ;
f:= i ;
End ;
Var
n: byte ;
Begin
n := 10 ;
Write (f(n , 2)) ;
Write (n) ;
End.
در تابع f ، متغير x بصورت پارامتر متغير تعريف شده است و i به صورت پارامتر مقداري.
با اجراي دستور Write اول ، عدد 2 نمايش داده ميشود و با اجراي دستور Write دوم عدد 4 نمايش داده ميشود.
نكته2 : در
زمان فراخواني تابع يا پردازه، پارامترهاي واقعي متناظر با پارامترهاي
متغير بايد متغير يا ثابت مجازي (ثابت نوع دار) باشد و استفاده از
ثابتهاي حقيقي يا عبارات محاسباتي در قسمت پارامتر واقعي، صحيح نيست. ولي
در مورد پارامترهاي مقداري اين محدوديت وجود ندارد.
فراخواني تابع و پردازه
توابع
را ميتوان در عبارات محاسباتي، مقايسهاي، دستورات خروجي و غيره فراخواني
نمود، كه در اين صورت ابتدا تابع فراخواني ميشود و خروجي تابع (مقدار
برگشتي آن) در عبارات استفاده ميگردد.
مثال :
Function f(i: byte) : Real ;
Begin
f := Sqrt(I) * 2 ;
End ;
Begin
Write (f(4) * 2 + 1) ;
End.
با اجراي اين برنامه، عدد 9 نمايش داده ميشود.
ولي
در فراخواني پردازه محدوديتهايي وجود دارد. به طور مثال نميتوان پردازه
را در عبارات محاسباتي، منطقي، دستورات خروجي و غيره فراخواني كرد و
زيرروالها عموماً بايد به طور مستقل و به عنوان يك دستور جداگانه،
فراخواني شوند.
مثال :
Var
n : Byte ;
Procedure p(Var x:Byte) ;
Begin
x := Sqr(x) + 2 ;
End ;
Begin
n := 2 ;
p(n) ; {فراخواني صحيح پردازه}
Write (n) ; {نمايش داده ميشود. 6 عدد}
n := p(n) * 2 ; {اين عبارت صحيح نيست.}
Write (p(n) ) ; {اين عبارت صحيح نيست.}
IF (p(n) = 2) Then {فراخواني رويه در عبارات شرطي صحيح نيست.}
Write (n) ;
End.
مثال فوق نمونهاي از فراخوانيهاي صحيح و اشتباه پردازه بود.
پردازهها و توابع بازگشتي
در زبان پاسكال امكان حل مسائل بازگشتي (Recursive)
وجود دارد. اين مسائل به نحوي هستند كه در تعريف مسئله دوباره مطرح
ميشوند. يك مثال ساده از توابع بازگشتي، تابع فاكتوريل است. براي محاسبه n! ميتوان n * (n – 1)! را محاسبه كرد. همان طور كه ديده ميشود دوباره تابع فاكتوريل در تعريف خود تابع ظاهر شد. براي محاسبه (n-1)! نيز ميتوان (n-1) * (n-2)! را
محاسبه كرده و به همين صورت الي آخر. اگر شرط پاياني در محاسبه تعيين نشود
اين محاسبه تا بينهايت ادامه خواهد يافت كه اين خود باعث بروز خطا ميشود
زيرا ميدانيم كه تابع فاكتوريل براي اعداد مثبت صحيح تعريف شده است. لذا
شرط پاياني را ميتوان 1! = 1 در نظر گرفت.
تابع زير فاكتوريل عدد ورودي n را محاسبه ميكند :
Function Fact(n:integer): integer ;
Begin
IF n <= 1 Then
Fact := 1
Else
Fact := n * Fact (n – 1) ;
End ;
در فراخوانيهاي بازگشتي، ذخيرهسازي متغيرهاي محلي، آدرس برگشت به روتين فراخوان و اطلاعات لازم ديگر در محلي به نام پشته (stack) در حافظه اصلي صورت ميگيرد. از آنجايي كه ظرفيت حافظة پشته ماكزيمم ميتواند 64 KB باشد لذا اگر تعداد فراخوانيهاي بازگشتي پي در پي افزايش يابد امكان پر شدن حافظه پشته و مسئله كمبود حافظه رخ ميدهد كه در صورت پر شدن حافظه پشته در زمان اجرا، پغام خطا «پشته سر ريز شد» (Stack overflow) نمايش داده ميشود و برنامه قطع ميگردد.
ساختار پيچيده به عنوان پارامتر
منظور
از ساختارهاي پيچيده، ساختارهايي مثل آرايه، ركورد، مجموعه و غيره است.
اگر لازم باشد كه پارامتر تابع يا زيرروال از نواع ساختارهاي پيچيده باشد،
ابتدا اين ساختارها بايد در قسمت Type و به عنوان يك نوع داده جديد تعريف شوند و سپس در ليست پارامترهاي تابع يا زيرروال، متغيرهايي از آن نوع تعريف شود.
مثال :
Type
a = Array[1. . 10] of Byte ;
m = Record
c : Byte ;
d : Char ;
End ;
Procedure p(s:a ; w:m) ;
Begin
.
.
.
End ;
Function f(k:a) : Byte ;
Begin
.
.
.
End ;
همان طور كه قطعه برنامة فوق نشان ميدهد، زيرروال p ، داراي يك پارامتر آرايهاي و يك پارامتر ركوردي است كه آرايه و ركورد مورد نظر ابتدا در قسمت Type به عنوان ك نوع دادة جديد تعريف شدهاند. تابع f هم
داراي يك پارامتر آرايهاي است. نقاط و مسائل مطرح شده در مورد پارامترهاي
مقداري و متغير، در مورد پارامترهاي پيچيده(آرايه، ركورد و …) نيز برقرار است.
بايد دقت داشت كه تعاريفي نظير :
Procedure pp(s: Array[1 .. 10] of char) ;
Function ff(k: Record
c: Byte ;
b: Char ;
End ; ) : Byte ;
Procedure p_p(Type s=Array[1 .. 10] of byte) ;
همگي اشتباه هستند و براي ارسال آرايه و ركورد و ساير ساختارهاي پيچيده بهتر است از روش ذكر شده استفاده گردد.
نكته1 : نوع بازگشتي يك تابع نميتواند از نوع ساختارهاي پيچده باشد.
مثال :
Type
s = Array[1 .. 10] of Byte ;
Function f(m:s): s ;
Begin
.
.
.
End ;
تعريف تابع f صحيح نيست، زيرا از نوع بازگشتي تابع، بايد از نوعهاي ساده باشد.
نكته2 : در
ارسال ساختارهاي پيچيده به زيربرنامهها بهتر است پارامترهاي متناظر با
آنها به صورت پارامتر متغير تعريف شود، زيرا با اين كار در مصرف حافظه
صرفهجويي ميشود.
رشته به عنوان پارامتر تابع و رويه
براي تعريف پارامترهاي رشتهاي به دو صورت ميتوان عمل كرد:
1ـ در تعريف پارامتر از كلمه String بدون ذكر كردن طول رشته استفاده كرد كه در اين صورت با پارامترهاي رشتهاي همانند انواع ساده كار ميشود:
مثال :
1) Procedure p_1(s: String ; Var m:String) ;
2) Function f_1(s:String ; Var m:String): Byte ;
3) Function f_2(i:Char): string ;
اگر در پارامترهاي رشتهاي از كلمة String به همراه طول مورد نياز استفاده كنيم، ابتدا بايد در قسمت Type نوع داده جديد از String تعريف كنيم سپس در ليست پارامترهاي زيربرنامه، پارامترهاي مورد نياز را تعريف نمائيم.
مثال :
Type
m = String[10] ;
t = String[20] ;
1) Procedure p_1(s:m Var k:t) ;
2) Function f_1(s:t Var w:t): Byte ;
3) Function f_2(I:integer): t ;
بايد دقت داشت كه تعاريفي نظير Function f(s:String[20]):Byte در پاسكال پذيرفته شده نيست.
نكته : در
فراخواني زيربرنامهها به همراه پارامترهاي رشتهاي، اگر پارامتر رشتهاي
به صورت مقداري تعريف شده باشد، پارامتر واقعي متناظر با آن پارامتر
رشتهاي ميتواند هم طول پارامتر مقداري نباشد ولي اگر پارامتر رشتهاي به
صورت پارامتر متغير تعريف شده باشد، پارامتر واقعي متناظر با آن بايد هم
طول پارامتر متغير باشد.