Logo

ماركو كانتو:
أساسي باسكال

الفصل 6
الإجراءات والوظائف

فكرة أخرى مهمة ركّزت عليها باسكال هي مفهوم الإجرائيات routine. مبدئيا هي سلسلة من التعليمات تحت اسم خاص غير مكرّر، و التي يمكن تنشيطها في كل مرّة باستخدام اسمها. بهذه الطريقة تتجنب معاودة كتابة التعليمات مرّة بعد أخرى، فتتحصّل على نسخة واحدة من التوليف يمكنك بسهولة تعديله لصالح كامل البرنامج. من وجهة النظر هذه، يمكنك أن تنظر إلى الإجرائيات كآلية أساسية لتغليف التوليف. سأعود إلى هذا الموضوع لاحقا مع مثال بعد أن أقدم أولا الصيغة النحوية syntax لإجرائيات باسكال.

إجراءات و وظائف باسكال

في باسكال، الإجرائية routine يمكن افتراضها بشكلين: إجراء procedure و وظيفة function. نظريا، الإجراء هو عملية تقوم أنت كمبرمج بسؤال الحاسب كي ينجزها، الوظيفة هي حسبة تردّ قيمة. هذا الفرق يؤكّده حقيقة أن الوظيفة لها نتيجة result، قيمة مسترجعة، بينما الإجراء ليس كذلك. كلا النوعين من الاجرائيات يكمن أن يكون لهما عدّة محدّدات parameters، من أنواع بيانات تعطى لها.

عمليا، الفرق عموما بين الوظائف و الإجراءات محدود جدا: يمكنك استدعاء وظيفة لإنجاز عمل ما ثم تتخطّى النتيجة (التي قد تكون رمز خطأ اختياري أو ما شابه) كما بإمكانك إستدعاء إجراء يمرّر نتيجته ضمن محدّداته (سيأتي الحديث أكثر عن المحدّدات بالإشارة reference parameters لاحقا في هذا الفصل).

ها هنا تعريفات لإجراء و نسختين من نفس الوظيفة، باستخدام صيغ مختلفة قليلا:

procedure Hello;
begin
  ShowMessage ('Hello world!');
end;

function Double (Value: Integer) : Integer;
begin
  Double := Value * 2;
end;

// or, as an alternative
function Double2 (Value: Integer) : Integer;
begin
  Result := Value * 2;
end;

استخدام result بدلا من اسم الوظيفة من أجل تخصيص قيمة الوظيفة المرتجعة أصبحت شائعة جدا، و تنحى لجعل التوليف أكثر مقروئية، حسب رأيي.

حالما يتم تعريف هذه الإجرائات، يمكنك إستدعائهم مرة أو أكثر. تستدعي الإجراء لجعله ينجز مهمته، و تستدعي الوظيفة لحساب القيمة:

procedure TForm1.Button1Click (Sender: TObject);
begin
  Hello;
end;
 
procedure TForm1.Button2Click (Sender: TObject);
var
  X, Y: Integer;
begin
  X := Double (StrToInt (Edit1.Text));
  Y := Double (X);
  ShowMessage (IntToStr (Y));
end;

ملاحظة: في الوقت الراهن لا تهتم كثيرا بصيغة الإجراءات أعلاه، و التي هي حقيقة مسارات methods. ببساطة قم بوضع زرّين على نافذة دلفي، لمسة مزدوجة فوقهما وقت التصميم، و ستقوم بيئة دلفي IDE بتوليد ما يناسب من توليف داعم: الآن و ببساطة عليك ملء الأسطر بين begin و end. لتجميع التوليف أعلاه تحتاج أيضا إلى إضافة خانة كتابة Edit للنافذة.

الآن يمكننا العودة إلى مفهوم تغليف التوليف الذي أشرت إليه سابقا. عندما تستدعي وظيفة Double، أنت لا تحتاج إلى معرفة الخوارزمية التي أُستخدمت لتنفيذها. إذا وجدت لاحقا طريقة أفضل لمضاعفة الأرقام، يمكنك بسهولة تغيير توليف الوظيفة، لكن التوليف الذي قام بالإستدعاء سيبقى ثابتا (بالرغم من أن التنفيذ سيكون أسرع!). نفس المفهوم يمكن تطبيقه على وظيفة Hello: يمكننا تعديل مخرجات البرنامج بتغيير التوليف داخل هذه الوظيفة، و بطريقة آلية سيتغير التأثير الذي يحدثه مسار Button2Click بدون أن نغيّر فيه:

procedure Hello;
begin
  MessageDlg ('Hello world!', mtInformation, [mbOK]);
end;

فائدة: عندما تستدعي إحدى وظائف أو إجراءات دلفي، أو أي مسار لمكوّن VCL، يجب أن تتذكر عدد و نوع المحدّدات. محرّر دلفي يمكن أن يساعد باقتراحه لقائمة المحدّدات الخاصة بالوظيفة أو الإجراء بواسطة تلميحة محادية حالما تقوم بطباعة اسم الاجرائية و تفتح قوسا. هذه الخاصية تدعى Code Parameters و هي جزءا من تقنية Code Insight.

المحدّدات بالإشارة

تسمح لك إجرائيات باسكال بتمرير المحدّدات parameter بقيمتها by value و بالإشارة by reference. افتراضيا المحدّدات يتم تمريرها بالقيمة: يتم نسخ القيمة في الصفّ stack و تقوم الإجرائيات باستخدام و معالجة النسخة، و ليست القيمة الأصلية.

تمرير المحدّد بالإشارة يعني أن قيمته لايتم نسخها في الصفّ لدي الإجرائية (تجنبّ النسخ دائما يعني أن تنفيذ البرنامج يكون أسرع). بدلا من ذلك، البرنامج يشير إلى القيمة الأصلية، يحدث هذا أيضا في توليف الإجرائية. هذا يسمح للإجراء أو الوظيفة بأن تغيّر في قيمة المحدّد. المحدّد المُرّر بالإشارة يُعبّر عنه بالمصطلح var .

هذا الأسلوب موجود في معظم لغات البرمجة. هو ليس موجودا في س، و لكن تم ادخاله في س++، حيث تقوم باستعمال علامة & (تمرير بالإشارة). في فيجوال بيسك كل محّدد لا يكون بصفة ByVal يتم تمريره بالإشارة.

هنا مثال لتمرير محدّد بالإشارة باستخدام مصطلح var :

procedure DoubleTheValue (var Value: Integer);
begin
  Value := Value * 2;
end;

في هذه الحالة، المحدّد تم استخدامه لغرضين، لتمرير قيمة للإجرائية و لإسترجاع القيمة الجديدة للتوليف الذي قام بالاستدعاء. عندما تكتب:

var
  X: Integer;
begin
  X := 10;
  DoubleTheValue (X);

فإن قيمة المتغير X تغدو 20، لأن الإجرائية تتعامل مع إشارة لموقع الذاكرة الأصلي ل X ، مؤثّرة في قيمتها الأولى.

تمرير المحدّدات بالإشارة له مايبرّره فيما يتعلّق بالأنواع التراتبية ordinal، و الجُمل strings بالطريقة التقليدية، و بالتسجيلات records الضخمة. في الواقع إن كائنات objects دلفي دائما يتم تمريرها بالقيمة، لأنها هي نفسها إشارة. لهذا السبب فإن تمرير الكائنات بإشارتها لامعنى له تقريبا (ما عدا بعض الحالات الخاصة جدا)، لأنها كما لوكانت "تمرير إشارة بالإشارة".

جُمل strings دلفي الضخمة لها سلوك مختلف بعض الشيء: هي تتصرّف و كأنها إشارة، لكنك إذا قمت بتغيير واحدة من متغيرات الجمل التي تشير إلى نفس الجملة في الذاكرة، يتم نسخها قبل تحديثها. الجُمل الطويلة التي تمرر كمحدد بقيمة تتصرف و كأنها إشارة فقط من حيث استخدام الذاكرة و سرعة الشغيل. لكن إذا قمت بتعديل قيمة الجملة ، فإن القيمة الأصلية لاتتأثّر، بالمقابل، إذا مرّرت الجُملة الطويلة بالإشارة، يمكنك تغيير القيمة الأصلية.

أدخلت دلفي 3 نوعا جديدا من المحدّدات، وهي out. محدّد out ليس لديه قيمة ابتدائية و يستخدم فقط لترجيع قيمة. هذه المحدّدات يجب استخدامها فقط لإجرائيات و وظائف COM ؛ عموما، من الأفضل التشبّت بمحدّدات var الأكثر فعالية. محددات out تتصّرف مثل محدّدات var بإستثناء عندما لا يوجد لديها قيمة ابتدائية.

محدّدات الثوابت

كبديل للمحددات بالإشارة، يمكنك استعمال محدد const. بما أنّه لايمكنك تخصيص قيمة لمحدد ثابت داخل الإجرائية، يمكن للمجمّع تحسين كفاءة تمرير المحدّد. المجمّع يمكن أن يختار أسلوبا شبيها بالمحددات بالإشارة (أو الإشارة لثابت const reference حسب مصطلحات س++)، لكن التصرّف سيبقى شبيها بالمحددات بالقيمة، لأن القيمة الأصلية لن تتأثر بالإجرائيات.

في الواقع، إذا حاولت تجميع التوليف (السخيف) التالي، ستقوم دلفي باصدار خطأ:

function DoubleTheValue (const Value: Integer): Integer;
begin
  Value := Value * 2;      // compiler error
  Result := Value;
end;

محدّدات المصفوفة المفتوحة

عكس لغة س، وظيفة أو إجراء دلفي لديهما دائما عددا ثابتا من المحدّدات, إلا أنه توجد طريقة لتمرير عددا غير ثابت من المحدّدات إلى الإجرائية بإستخدام المصفوفة المفتوحة open array.

التعريف الأساسي لمحدد مصفوفة مفتوحة open array parameter هو مصفوفة مفتوحة ذات نوع. هذا يعني انك تشير إلى نوع المحدّد لكنك لاتعرف كم عنصر من هذا النوع سيكون لدى المصفوفة. هنا مثال لمثل هذا التعريف:

function Sum (const A: array of Integer): Integer;
var
  I: Integer;
begin
  Result := 0;
  for I := Low(A) to High(A) do
    Result := Result + A[I];
end;

بإستخدام High(A) يمكننا الحصول على حجم المصفوفة، لاحظ أيضا استخدام قيمة الترجيع في الوظيفة، Result، لتخزين قيم مؤقتة. يمكنك استدعاء هذه الوظيفة بأن تمرر إليها مصفوفة من التعبيرات ذات نوع صحيح Integer.

X := Sum ([10, Y, 27*I]);

إذا كان لديك مصفوفة من أي حجم ذات نوع صحيح، تستطيع تمريرها مباشرة لإجرائية تتطلب محدد بمصفوفة مفتوحة، أو بدلا من ذلك، يمكنك استدعاء وظيفة Slice لتمرير جزء فقط من المصفوفة (كما هو مشار اليه في ثاني محدد في الوظيفة). ها هنا مثال، حيث مصفوفة كاملة تمّ تمريرها كمحدّد:

var
  List: array [1..10] of Integer;
  X, I: Integer;
begin
  // initialize the array
  for I := Low (List) to High (List) do
    List [I] := I * 2;
  // call
  X := Sum (List);

إذا أردت تمرير فقط جزء من المصفوفة إلى وظيفة Sum، ببساطة قم باستدعائها بالطريقة التالية:

X := Sum (Slice (List, 5));

تستطيع أن تجد كل أجزاء التوليف الذي تم عرضه في هذا القسم في مثال OpenArr (أنظر الشكل 6.1، لاحقا، بالنسبة للنموذج).

الشكل 6.1: مثال OpenArr عندما يتم الضغط على زرّ Partial Slice

المصفوفات المفتوحة النوعية في دلفي 4 متوافقة تماما مع المصفوفات الحيّة dynamic (تم تقديمها في دلفي 4 و مغطّاة في الفصل 8). المصفوفات الحيوية تستخدم نفس الصيغة في المصفوفات المفتوحة، مع اختلاف انه يمكنك استخدام التركيب array of Integer لتعريف متغيّر، و ليس فقط لتمرير محدّد.

محدّدات مصفوفة مفتوحة نوع متباين

بجانب هذه المصفوفات المفتوحة النوعية، تسمح لك دلفي بتحديد مصفوفات مفتوحة نوع متباين type-variant أو بلا نوع. هذا النوع الخاص من المصفوفات لديه عدد غير محدود من القيم، و التي يمكن الاستفادة منها لتمرير المحدّدات.

تقنيا، بنية مصفوفة الثوابت تسمح لك بتمرير مصفوفة بعدد غير محدود من العناصر من أنواع مختلفة إلى إجرائية دفعة واحدة. مثال ذلك، ها هنا تعريف لوظيفة Format (سنرى كيف نستخدم هذه الوظيفة في الفصل 7، عند الحديث عن الجمل):

function Format (const Format: string;
  const Args: array of const): string;

المحدّد الثاني هو مصفوفة مفتوحة، تستقبل عددا غير محدود من القيم. في الواقع، يمكنك استدعاء هذه الوظيفة بالطرق التالية:

N := 20;
S := 'Total:';
Label1.Caption := Format ('Total: %d', [N]);
Label2.Caption := Format ('Int: %d, Float: %f', [N, 12.4]);
Label3.Caption := Format ('%s %d', [S, N * 2]);

لاحظ أنه بإمكانك تمرير المحدّد كقيمة ثابت، أو قيمة متغير، أو كتعبير. تعريف وظيفة من هذا النوع أمر سهل، لكن كيف تقوم بتوليفه؟ كيف تتعرف على نوع المحدّدات؟ ان قيم محددات مصفوفة مفتوحة نوع متباين هي متوافقة مع عناصر نوع TVarRec.

ملاحظة: لا تخلط بين تسجيلة TVarRec و تسجيلة TVarData المستخدمة من قبل نوع Variant نفسه. هاتان البنيتان تخدمان أغراضا مختلفة و ليستا متوافقتين. بالرغم من أن قائمة الأنواع المحتملة مختلفة، لأن TVarRec يمكن ان تضم أنواع بيانات دلفي، بينما TVarData يمكن أن تحوي أنواع بيانات OLE.

تسجيلة TVarRec لها البُنية التالية:

type
  TVarRec = record
    case Byte of
      vtInteger:    (VInteger: Integer; VType: Byte);
      vtBoolean:    (VBoolean: Boolean);
      vtChar:       (VChar: Char);
      vtExtended:   (VExtended: PExtended);
      vtString:     (VString: PShortString);
      vtPointer:    (VPointer: Pointer);
      vtPChar:      (VPChar: PChar);
      vtObject:     (VObject: TObject);
      vtClass:      (VClass: TClass);
      vtWideChar:   (VWideChar: WideChar);
      vtPWideChar:  (VPWideChar: PWideChar);
      vtAnsiString: (VAnsiString: Pointer);
      vtCurrency:   (VCurrency: PCurrency);
      vtVariant:    (VVariant: PVariant);
      vtInterface:  (VInterface: Pointer);
  end;

كل تسجيلة محتملة لديها حقل نوع VTipe ، بالرغم انه ليس سهلا رؤيته من المرة الأولى لأن تعريفه يتم مرة واحدة فقط.

بواسطة هذه المعلومات يمكننا فعلا كتابة وظيفة قادرة على التعامل مع أنواع بيانات مختفلة. في مثال وظيفة SumAll ، أريد أن أكون قادرا على جمع قيم من أنواع مختلفة، تحويل الجمل إلى أعداد صحيحة، الحروف إلى ما يقابلها من قيمة ترتيبية، و إضافة 1 للقيم البولية الموجبة. التوليف يعتمد على تعليمة case، و يعدّ سهلا، بالرغم من أنه علينا التعامل مع المؤشرات pointers أكثر من مرّة:

function SumAll (const Args: array of const): Extended;
var
  I: Integer;
begin
  Result := 0;
  for I := Low(Args) to High (Args) do
    case Args [I].VType of
      vtInteger: Result :=
        Result + Args [I].VInteger;
      vtBoolean:
        if Args [I].VBoolean then
          Result := Result + 1;
      vtChar:
        Result := Result + Ord (Args [I].VChar);
      vtExtended:
        Result := Result + Args [I].VExtended^;
      vtString, vtAnsiString:
        Result := Result + StrToIntDef ((Args [I].VString^), 0);
      vtWideChar:
        Result := Result + Ord (Args [I].VWideChar);
      vtCurrency:
        Result := Result + Args [I].VCurrency^;
    end; // case
end;

لقد اضفت هذا التوليف إلى مثال OpenArr، الذي يستدعي وظيفة SumAll عند يتم الضغط على زرّ معيّن.

procedure TForm1.Button4Click(Sender: TObject);
var
  X: Extended;
  Y: Integer;
begin
  Y := 10;
  X := SumAll ([Y * Y, 'k', True, 10.34, '99999']);
  ShowMessage (Format (
    'SumAll ([Y*Y, ''k'', True, 10.34, ''99999'']) =     >  %n', [X]));
end;

يمكن رؤية نتاج هذا الاستدعاء، و النموذج بمثال OpenArr، في الشكل 6.2.

الشكل 6.2: النموذح في مثال OpenArr، مع مربع رسالة تُعرض عند الضغط على زرّ Untyped.

طرق الإستدعاء في دلفي

أدخلت نسخة 32-بت في دلفي مفهوما جديدا لتمرير المحدّدات، تعرف باسم fastcall: فحيثما أمكن، و حتى لثلاث محدّدات يمكن تمريرها في مسجِّلات registers المعالج، جاعلة من استدعاء الوظيفة أسرع بكثير. طريقة الاستدعاء السريع fast calling convention (تستخدم افتراضيا في دلفي 3) يشار لها بمصطلح register.

المشكلة أنها الطريقة الإفتراضية، و الوظائف التي تستخدمها ليست متوافقة مع ويندوز: وظائف Win32 يجب تعريفها باستخدام طريقة استدعاء sdtcall، و هي مزيج من الطريقة الأصلية للإستداعاء في باسكال لوظائف Win16 و طريقة استدعاء cdecl في لغة س.

لا يوجد عموما سبب يمنع استعمال طريقة الإستدعاء السريع، إلا إذا كنت تقوم باستدعاءات ويندوز خارجية external أو تقوم بتحديد وظائف callback. سوف نرى مثالا عن استخدام طريقة stdcall قبل نهاية هذا الفصل, يمكنك أن تجد ملخّصا لطرق استدعاءات دلفي تحت موضوع Calling conventions في ملف مساعدة دلفي.

ماهو المسار؟

إذا سبق لك بالفعل العمل بدلفي أو قرأت أدّلة التشغيل، فمن المحتمل انك سمعت بمصطلح method مسار. المسار هو نوع خاص من الوظائف أو الإجرائيات ذات علاقة بنوع بيانات، الطبقة class. في دلفي، كل مرة نتناول حدثا، نحتاج لتعريف مسار، أو بصفة عامة إجراء. على أية حال، مصطلح مسار يستخدم للإشارة إلى الوظائف و الإجراءات ذات العلاقة بالطبقة class.

سبق لنا أن رأينا بالفعل عددا من المسارات في الأمثلة الواردة في هذا الفصل و في ما سبقه. في ما يلي مسار فارغ أضيف آليا بواسطة دلفي للتوليف المصدري لنموذج:

procedure TForm1.Button1Click(Sender: TObject);
begin
  {here goes your code}
end;

التعريف المسبق

عندما تحتاج إلى استخدام معرّف identifier (من أي نوع)، يجب أن يكون المجمّع قد رأى بالفعل تحديدا ما ليعلم إلى ماذا يشير هذا المعرّف. لهذا السبب، فأنت عادة ما تقدّم تعريفا كاملا قبل استخدام أية إجرائية. على أية حال، توجد حالات لايمكنك فيها ذلك. إذا فرضنا أن الإجراء A يستدعي الإجراء B، و الإجراء B يستدعي الإجراء A، عندما تبدأ بكتابة التوليف، فستحتاج إلى مناداة إجرائية لا يزال المجمّع لم ير تعريفها.

إذا أردت تعريف وجود إجراء أو وظيفة بإسم معيّن و محدّدات معطاة، من غير تقديم توليفها الفعلي، يمكنك كتابة الإجراء أو الوظيفة متبوعة بالكلمة المفتاحية forward:

procedure Hello; forward;

لاحقا، يجب أن يقدم التوليف تعريفا كاملا للإجراء، لكن هذا يمكن استدعاؤه حتى قبل أن يتم تعريفه بالكامل. فيما يلي مثال ساذج، فقط لإعطائك فكرة:

procedure DoubleHello; forward;

procedure Hello;
begin
  if MessageDlg ('Do you want a double message?',
      mtConfirmation, [mbYes, mbNo], 0) = mrYes then
    DoubleHello
  else
    ShowMessage ('Hello');
end;

procedure DoubleHello;
begin
  Hello;
  Hello;
end;

هذه الطريقة تسمح لك بكتابة تواتر recursion متبادل: DoubleHello تنادي Hello، لكن Hello ربما تنادي DoubleHello، أيضا. طبعا لابد من وجود شرط لإيقاف التواتر، لتجنب فوران التكدّس stack overflow.

بالرغم من أن تعريف الإجراء المسبق forward procedure ليس شائعا في دلفي، توجد حالة مشابهة و التي تتكر أكثر. عندما تقوم بتعريف إجرائية أو وظيفة في جزء الواجهة interface في الوحدة unit (المزيد عن الوحدات في الفصل القادم)، يتم إعتباره تعريفا مسبقا، حتى إذا كان مصطلح forward غير ظاهر. فعليا لايمكنك أن تكتب جسم الاجرائية في جزء الواجهة. في نفس الوقت، يجب أن تقوم بتقديم التنفيذ الفعلي لكل إجرائية قمت بتعريفها، وذلك في نفس الوحدة.

نفس الشيء ينطبق على تعريف المسار methos داخل نوع طبقة class و الذي يتم توليده آليا بواسطة دلفي (كما يحدث عند اضافة حدث لنموذج أو أحد مكوناته). مناولات الحدث المعرّفة داخل طبقة TForm هي تعريفات مسبقة: حيث سيتم تقديم التوليف في جزء التنفيذ implementaion من الوحدة. فيما يلي مقطع من توليف مصدري لمثال سابق، مع تعريف لمسار Button1Click :

type
  TForm1 = class(TForm)
    ListBox1: TListBox;
    Button1: TButton;
    procedure Button1Click(Sender: TObject);
  end;

الأنواع الإجرائية

ميزة فريدة أخرى في اوبجكت باسكال وهي وجود الأنواع الإجرائية procedural types. في الواقع يعّد هذا موضوعا برمجيا متقدما، و قلّة من مبرمجي دلفي سوف يستعملونه باستمرار. عموما، ما دمنا سوف نناقش الموضوعات ذات العلاقة في الفصول القادمة (خاصة، مؤشرات المسار، التقنية المستخدمة بكثافة في دلفي)، فإن الأمر يستحق أن نلقي بنظرة سريعة على هذا الموضوع هنا. إذا كنت مبرمجا مبتدئا، يمكنك تخطّي هذا القسم مؤقتا، و أن تعود لاحقا عندما تشعر بأنك مستعدّ لذلك.

في باسكال، يوجد مفهوم النوع الإجرائي procedural type (و الذي يشبه مفهوم مؤشر الوظيفة function pointer في لغة س). تعريف النوع الإجرائي يشير إلى قائمة من المحدّدات و نوع الترجيع في حالة الوظيفة. مثلا، يمكنك تعريف نوع إجراء مع محدد برقم صحيح يتم تمريره بالإشارة مثل:

type
  IntProc = procedure (var Num: Integer);

النوع الإجرائي هذا متوافق مع أي إجرائية تملك تماما نفس المحددات (أو نفس توقيع الوظيفة function signature، بتعبير لغة س). هنا مثال لإجرائية متوافقة.

procedure DoubleTheValue (var Value: Integer);
begin
  Value := Value * 2;
end;

ملاحظة: في نسخة 16-بت من دلفي، يجب أن يتم تعريف الإجرائيات بإستخدام توجيه far من أجل إستعمالها كقيمة فعلية للنوع الإجرائي.

الأنواع الإجرائية يمكن استخدامها لغرضين مختلفين: يمكنك تعريف متغيرات من نوع إجرائي أو تمرير نوع إجرائي - مؤشّر وظيفة- كمحددات إلى إجرائية أخرى. بوجود النوع السابق و تعريفات الإجراء، يمكنك كتابة هذا التوليف:

var
  IP: IntProc;
  X: Integer;
begin
  IP := DoubleTheValue;
  X := 5;
  IP (X);
end;

هذا التوليف له نفس التأثير الذي للنسخة الأقصر التالية:

var
  X: Integer;
begin
  X := 5;
  DoubleTheValue (X);
end;

واضح أن النسخة الأولى أكثر تعقيدا، لذا لماذا علينا استعمالها؟ في بعض الحالات، القدرة على تقرير أية وظيفة يمكن استدعاؤها و أن يتم أستدعاؤها فعليا لاحقا، هذه الامكانية قد تكون مفيدة. يمكن بناء مثال متشعب يعرض هذا التوجه. عموما، أنا أفضّل أن أجعلك تستكشف مثالا بسيط بما يكفي، اسمه ProcType. هذا المثال أكثر تشعبا من كل ما سبق أن رأيناه حتى الآن، لجعل الأمور أكثر واقعية.

ببساطة قم بانشاء مشروع جديد و قم بوضع زرّي خيار radio buttons، زرّ ضغط، و ملصقين lables على النافذة. كما هو موضّح في الشكل 6.3. هذا المثال مبني على إجرائين. الإجراء الأول يتم استخدامه لمضاعفة قيمة المحدّد. هذا الإجراء شبيه بذلك الذي قمت بعرضه في هذا القسم. الإجراء الثاني يتم استخدامه لزيادة قيمة المحدد بثلاثة أضعاف، لهذا فإن اسمه TripleTheValue:

الشكل 6.3: نموذج مثال ProcType.

procedure TripleTheValue (var Value: Integer);
begin
  Value := Value * 3;
  ShowMessage ('Value tripled: ' + IntToStr (Value));
end;

الإجراءان يعرضان ماذا يجري فيهما، لكي يعلمانا بأنهما قد استدعيا. هذه ميزة تعرّف بسيطة يمكنك استخدامها لاختبار اذا ما تم تنفيذ جزء معين من التوليف أو متى تم ذلك، بدلا من اضافة نقاط اعاقة breakpoint فيهما.

في كلّ مرة يقوم فيها المستخدم بالضغط على زرّ Apply، يتم تنفيذ أحد الإجرائين، حسب حالة خاناتي الخيار. في الواقع، عندما يكون لديك إثنان من خانات الخيار في النموذج، واحدة منهما فقط يمكن إختيارها في نفس الوقت. يمكن لهذا التوليف ان يُنفّذ باختبار قيمة خانتي الخيار داخل التوليف الخاص بالحدث OnClick لزرّ Apply. لكن و من أجل استعراض كيفية استخدام الأنواع الإجرائية، قمت بدلا من ذلك باتّباع توجّه أطول لكن مثير للإهتمام. كلّ مرّة يضغط فيها المستخدم على واحدة من خانات الخيار، أحد الإجرائين يتم تخزينه في متغيّر:

procedure TForm1.DoubleRadioButtonClick(Sender: TObject);
begin
  IP := DoubleTheValue;
end;

عندما يضغط المستخدم على الزرّ، يتم تنفيذ الإجرائية التي يتم تخزينها:

procedure TForm1.ApplyButtonClick(Sender: TObject);
begin
  IP (X);
end;

من أجل السماح لثلاث وظائف مختلفة بتناول المتغيرين IP و X ، نحتاج لجعلهما مرئيين على مستوى النموذج form بالكامل؛ لايمكن تعريفهما محليا localy (داخل أحد المسارات). الحل لهذه المشكلة هي وضع المتغيرين داخل تعريف النموذج:

type
  TForm1 = class(TForm)
    ...
  private
    { Private declarations }
    IP: IntProc;
    X: Integer;
  end;

سوق نرى تماما ماذا يعني هذا في الفصل التالي، لكن حاليا، يتطلب منك الأمر تعديل التوليف الذي قامت دلفي بإعداده لنوع الطبقة كما هو موضّح أعلاه. و قم باضافة النوع الإجرائي الذي قمت بعرضه سابقا. من أجل تمهيد هذين المتغيرين بقيم مناسبة، يمكننا مناولة حدث OnCreate الخاص بالنموذج (اختر هذا الحدث في معاين الكائنات Object Inspector بعد تفعيل النموذج، أو قم ببساطة بضغط مزدوج على النموذج). اقترح أن تقوم بمراجعة التوليف لدراسة تفاصيله في المثال.

يمكنك مشاهدة مثال عملي لإستخدام الأنواع الإجرائية في الفصل 9، في قسم وظيفة Callback في ويندوز.

newالتحميل المضاف لوظيفة

فكرة التحميل المضاف overloading بسيطة: يسمح لك المجمّع compiler بتحديد وظيفتين أو إجرائين يحملان نفس الإسم، بشرط أن تختلف المحدّدات. و باختبار هذه المحددات، يمكن للمجمّ استنتاج أية نسخة من الإجرائيتين تريد استدعاؤها.

راجع هذه السلسة من الوظائف المستخرجة من وحدة Math في مكتبة VCL:

function Min (A,B: Integer): Integer; overload;
function Min (A,B: Int64): Int64; overload;
function Min (A,B: Single): Single; overload;
function Min (A,B: Double): Double; overload;
function Min (A,B: Extended): Extended; overload;

عندما تنادي Min (10,20)، يستنتج المجمّع ببساطة بأنك تقصد استدعاء الوظيفة الأولى من المجموعة، لذا فالقيمة المرتجعة ستكون رقما صحيحا.

القواعد الأساسية اثنان:

فيما يلي ثلاث نسخ بحمل مضاف لإجراء ShowMsg قمت باضافتها لمثال OverDef (التطبيق الذي يستعرض الحمل المضاف و المحددات الافتراضية)

procedure ShowMsg (str: string); overload;
begin
  MessageDlg (str, mtInformation, [mbOK], 0);
end;

procedure ShowMsg (FormatStr: string;
  Params: array of const); overload;
begin
  MessageDlg (Format (FormatStr, Params),
    mtInformation, [mbOK], 0);
end;

procedure ShowMsg (I: Integer; Str: string); overload;
begin
  ShowMsg (IntToStr (I) + ' ' + Str);
end;

الوظائف الثلاثة تعرض نافذة رسالة مع جملة، بعد صياغة متكررة للجملة بعدة طرق. ها هنا النداءات الثلاث للبرنامج:

ShowMsg ('Hello');
ShowMsg ('Total = %d.', [100]);
ShowMsg (10, 'MBytes');

ما ادهشني ايجابيا هو ان تقنية Code Parameters محددات التوليف في دلفي تعمل بصورة رائعة مع محددات الحمل المضاف. فمع بداية فتح قوس في طباعتك بعد اسم الإجرائية، يتم عرض كل البدائل المتوفرة. و مع ادخالك للمحدد، تقوم دلفي باختبار نوعه لتقرير أيا من البدائل لايزال متوفرا. في الشكل 6.4 يمكنك رؤية هذا بعد البدء بكتابة ثابت جملة تعرض دلفي نسخة متوافقة واحدة (تستثني نسخة إجراء ShowMsg الذي لها نوع صحيح كأول محدد).

الشكل 6.4: البدائل المتعددة التي اقترحتها تقنية Code Parameters لإجرائيات الحمل المضاف، مفروزة بحسب المحددات المتوفرة فعلا.

حقيقة أن كل نسخة من إجرائية حمل مضاف overloaded يجب أن تكون معلّمة بوضوح؛ هذا يعني ضمنا أنك لاتستطيع تحميل إجرائية موجودة في نفس الوحدة unit وليست معّلّمة بمصطلح overload. (رسالة الخطأ التي تظهر لك عندما تحاول ذلك هي: تعريف سابق ل <اسم الاجرائية> ليست معلمّة بتوجيه 'overload'.) عموما ، يمكنك اجراء تحميل اضافي لإجرائية تكون قد سبق تعريفها في وحدة مختلفة. هذا لأجل التوافقية مع النسخ السابقة من دلفي، و التي تسمح لعدة وحدات أن تعيد استخدام نفس اسم الإجرائية. لاحظ، على أية حال، بأن هذه الحالة الخاصة ليست ميزة اضافية للتحميل المضاف، لكنها اشارة الى المشكلات التي قد تواجهها.

مثلا، يمكنك اضافة التوليف التالي للوحدة:

procedure MessageDlg (str: string); overload;
begin
  Dialogs.MessageDlg (str, mtInformation, [mbOK], 0);
end;

هذا التوليف لايقوم فعلا بحمل اضافي لإجرائية MessageDlg الأصلية. في الواقع إذا كتبت:

MessageDlg ('Hello');

سوف تتحصل على رسالة خطأ لطيفة تشير إلى غياب بعض المحددات. الطريقة الوحيدة لإستدعاء نسخة محلية بدلا من أخرى تابعة لمكتبة VCL هي في أن تشير صراحة إلى الوحدة المحليّة، الأمر الذي يخدش فكرة التحميل المضاف:

OverDefF.MessageDlg ('Hello');

new المحدّدات الافتراضية

خاصّية جديدة أخرى في دلفي 4 و هي أنك تستطيع أن تعطي قيمة افتراضية default لمحدد إجراء أو وظيفة، و يمكنك استدعاء هذه الوظيفة رفق المحدد أو بدونه. دعني أعرض مثالا. يمكننا تحديد التغليف التالي لمسار method MessageBox الخاص بالكائن العام Application، و الذي يستخدم أنواع PChars بدلا من جمل strings، و سوف نوفّر محددين افتراضيين:

procedure MessBox (Msg: string;
  Caption: string = 'Warning';
  Flags: LongInt = mb_OK or mb_IconHand);
begin
  Application.MessageBox (PChar (Msg),
    PChar (Caption), Flags);
end;

بهذا التعريف، يمكننا استدعاء الاجرائية بأي من الطرق التالية:

MessBox ('Something wrong here!');
MessBox ('Something wrong here!', 'Attention');
MessBox ('Hello', 'Message', mb_OK);

في الشكل 6.5 يمكنك رؤية محددات التوليف Code Parameters لدلفي يستخدم نمط مختلف و مناسب ليشير الى المحددات التي لها قيم افتراضية، بحيث تستطيع بسهولة تبيان أي المحددات التي يمكنك استبعادها.

الشكل 6.5: محددات التوليف لدلفي تشير بين أقواس مربعة الى المحددات التي لها قيم افتراضية؛ و التي يمكنك استبعادها في هذا الاستدعاء.

لاحظ ان دلفي لا تقوم بتوليد أي توليف خاص لدعم المحددات الافتراضية؛ كما لا تنشىء نُسخا متعدّدة من الإجرائية. ببساطة، المحددات المستبعدة يتم اضافتها من قبل المجمّع إلى التوليف الذي قام بالإستدعاء.

هناك قيد واحد مهم يؤثّر على استخدام المحدّدت الإفتراضية: لا يمكنك تخطّي المحدّدات. مثلا، لا تستطيع تمرير المحدّد الثالث للوظيفة بعد استبعادك للمحدّد الثاني:

MessBox ('Hello', mb_OK); // error  

هذه هي القاعدة الرئيسية للمحددات الافتراضية: حين الاستدعاء، يمكنك فقط استبعاد المحددات بدءا من المحدد الأخير. بعبارة أخرى، إذا استبعدت محدّدا يجب أيضا استبعاد ما يليه.

هناك أيضا بعض القواعد الأخرى للمحددات الافتراضية:

استخدام محددات افتراضية و حمل مضاف في نفس الوقت يمكن ان يسبب عدّة مشاكل، بسبب امكانية تعارض الميزتين. مثال ذلك، إذا ما اضفت للمثال السابق النسخة الجديدة التالية من إجرائية ShowMsg:

procedure ShowMsg (Str: string; I: Integer = 0); overload;
begin
  MessageDlg (Str + ': ' + IntToStr (I),
    mtInformation, [mbOK], 0);
end;

عندها المجمّع لن يتذمر إنّه تعريف صحيح. لكن الإستدعاء:

ShowMsg ('Hello');

يتم اعتباره من قبل المجمّع على أنه استدعاء حمل مضاف ملتبس Ambiguous overloaded call to 'ShowMsg'. لاحظ أن هذا الخطأ يظهر في سطر التوليف الذي تم تحويله بطريقة صحيحة قبل تعريف الحمل المضاف الجديد. عمليا، ليس لدينا أية طريقة لاستدعاء أجراء ShowMsg بمحدد جملة واحد، حيث أن المحوّل لا يعرف إذا كنا نريد استدعاء النسخة التي بمحدد جملة واحد فقط أو تلك التي بمحدد جملة و محدد رقم صحيح ذو قيمة افتراضية. عندما يكون لديه مثل هذا الشكّ، المحوّل يتوقّف و يسأل المبرمج أن يبيّن قصده بوضوح أكثر.

ملخّص

كتابة الإجراءات و الوظائف هو العنصر الأساسي في البرمجة، بالرغم من أنك في دلفي سوف تتجه لكتابة المسارات methods -- إجراءات و وظائف مرتبطة بالطبقات classes و الكائنات objects.

بدلا من التنقّل إلى خصائص الإتجاه الكائني object-oriented، الفصول القليلة القادمة تقدّم لك بعض التفاصيل عن عناصر أخرى في برمجة باسكال، مبتدئين بالجُمل strings.

الفصل التالي: مناولة الجُمل

حقوق النسخ محفوظة لماركو كانتو؛ وينتش ايطاليا © Copyright Marco Cantù, Wintech Italia Srl 1995-2000
حقوق الترجمة: خالد الشقروني ، 2000