Logo

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

الفصل 7
مناولة الجُمل

مناولة الجُمل strings في دلفي أمر بسيط، لكن وراء الكواليس؛ الحالة معقدة بعض الشيء. لباسكال طريقتها التقليدية لمناولة الجُمل، ويندوز لها طريقتها الخاصة، المستمدة من لغة س. نسخ دلفي 32-بت تضمنت نوع بيانات قوي لجمل طويلة، و التي تشكل النوع الافتراضي لنوع جملة string في دلفي.

أنواع الجُمل

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

نوع الجملة يشبه نوع مصفوفة. في الواقع، ان الجملة هي تقريبا مصفوفة من أحرف. ما يبيّن هذا؛ حقيقة أنه يمكنك الوصول إلى حرف معيّن في الجملة باستخدام تركيبة [].

لتجاوز قيود جمل باسكال التقليدية، قامت نسخ 32-بت من دلفي بدعم الجمل الطويلة. يوجد في الواقع ثلاث أنواع جمل:

استخدام الجمل الطويلة

اذا استخدمت ببساطة نوع جملة طويلة، فإنك ستتحصّل إما على جمل قصيرة أو جمل ANSI، و ذلك حسب قيمة التوجيه $H للمجمّع. قيمة $H+ (و هي القيمة الافتراضية) تعني جمل طويلة (نوع ANSIString)، و هو المستخدم من قبل مكوّنات دلفي.

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

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

SetLength (String1, 200);

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

نادرا ما تدعو الحاجة إلى تحديد طول لجملة. الحالة الوحيدة التي يجب فيها أن تقوم بتخصيص ذاكرة لجملة طويلة باستخدام SetLenth هي عندما يتطلب الأمر منك تمرير جملة كمحدد الى وظيفة API (بعد تلبيس نوع مناسب)، كما سأقوم بعرضه بعد قليل.

النظر إلى الجُمل في الذاكرة

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

Str1 := 'Hello';
Str2 := Str1;

بجانب التعامل مع الجمل، يقوم البرنامج بعرض حالتها الداخلية في مربّع قائمة listbox، مستعملا وظيفة StringStatus التالية:

function StringStatus (const Str: string): string;
begin
  Result := 'Address: ' + IntToStr (Integer (Str)) +
    ', Length: ' + IntToStr (Length (Str)) + 
    ', References: ' + IntToStr (PInteger (Integer (Str) - 8)^) +
    ', Value: ' + Str;
end;

أمر حيوي في وظيفة StringStatus أن يتم تمرير محدد الجملة كمحدد ثابت. ان تمرير هذا المحدد بواسطة النسخ سينتج عنه آثارا جانبية لوجود إشارة أخرى اضافية للجملة في نفس وقت تنفيذ الوظيفة. بالمقابل، ان تمرير المحدد بطريق الإشارة (var) أو ثابت (const) لا يسبب في خلق إشارة إضافية للجملة. لقد استعملت في هذه الحالة محدد const، حيث ليس من المفترض أن تقوم الوظيفة بتعديل الجملة.

للحصول على عنوان موقع الجملة في الذاكرة (مفيد لمعرفة صفتها الفعلية و لرؤية متى تقوم جملتان بالإشارة إلى نفس منطقة الذاكرة)، قمت ببساطة و في عمق التوليف بتلبيس نوع typecast من نوع جملة إلى نوع صحيح. الجمل عمليا هي إشارات، هي مؤشّرات: قيمتها تحوي موقع الذاكرة الفعلي للجملة.

من أجل إستخراج عدد الإشارات، جعلت التوليف يعتمد على حقيقة غير معروفة عند الكثيرين و هي أن الطول و عدد الإشارات يتم تخزينها فعليا في الجملة، قبل النصّ الفعلى وقبل الموقع الذي يشير اليه متغيّر الجملة. الموقع (بالسالب) هو -4 و فيه طول الجملة (قيمة يمكنك استخراجها بسهولة أكبر باستعمال وظيفة Length) و -8 و فيه عدد الإشارات.

تذكّر بأن هذه المعلومات الداخلية المتعلقة بمواقع الذاكرة offsets يمكن أن تتغيّر في النسخ المستقبلية من دلفي؛ أيضا لا يوجد أية ضمانة بأن خصائص مشابهة و غير موثّقة سيتم الاحتفاظ بها مستقبلا.

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

الشكل 7.1: مثال StrRef يعرض الحالة الداخلية لجملتين، بما في ذللك العدد الحالي للإشارة

يمكننا فعليا توليد هذا التأثير، المبيّن في القسم الثاني من القائمة في الشكل 7.1، من خلال كتابة التوليف التالي للحدث OnClick للزرّ الثاني:

procedure TFormStrRef.BtnChangeClick(Sender: TObject);
begin
  Str1 [2] := 'a';
  ListBox1.Items.Add ('Str1 [2] := ''a''');
  ListBox1.Items.Add ('Str1 - ' + StringStatus (Str1));
  ListBox1.Items.Add ('Str2 - ' + StringStatus (Str2));
end;

لاحظ ان التوليف الخاص بمسار BtnChangeClick لا يمكن تنفيذه إلا بعد مسار BtnAssignClick. و لضمان هذا، يبدأ البرنامج و الزرّ الثاني في حالة خمود disabled (سمة Enabled قيمتها سالبة False)؛ ثم يعيد البرنامج تمكين الزرّ مع نهاية المسار الأول. يمكنك التوسع بحرية في هذا المثال و استخدام وظيفة StringStatus لاستكشاف سلوك الجمل الطويلة في ظروف اخرى متعددة.

جُمل دلفي و PChars في ويندوز

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

مثال ذلك، لنسخ عنوان نموذج و وضعها في جملة PChar (باستخدام وظيفة API: وهي GetWindowText) ثم نسخها لعنوان زرّ، يمكنك كتابة التوليف التالي:

procedure TForm1.Button1Click (Sender: TObject);
var
  S1: String;
begin
  SetLength (S1, 100);
  GetWindowText (Handle, PChar (S1), Length (S1));
  Button1.Caption := S1;
end;

يمكنك ايجاد هذا التوليف في مثال LongStr. لاحظ انك إذا كتبت هذا التوليف و اغفلت عن تخصيص ذاكرة للجملة بواسطة SetLength، فان البرنامج سينهار غالبا. إذا قمت باستخدام PChar من أجل تمرير قيمة (و ليس لإستقبال قيمة كما في التوليف أعلاه)، سيكون التوليف أكثر سهولة، لأنه لا توجد حاجة لتعريف جملة مؤقتة و تمهيدها. سطر التوليف التالي يقوم بتمرير سمة Caption الخاصة بملصق Label كمحدد لوظيفة API، ببساطة بتلبيس نوعها لنوع PChar:

SetWindowText (Handle, PChar (Label1.Caption));

عندما تحتاج لتلبيس جملة عريضة WideString الى نوع يتوافق مع ويندوز، عليك استخدام PWideChar بدلا من PChar لغرض التحويل. الجمل العريضة غالبا ما تستخدم في برامج تستخدم تقنيات OLE و COM.

بعد أن أبرزت الصورة المشرقة، الآن أريد أن أركّز على الشراك المنصوبة. هناك بعض المشاكل التي يمكن ان تبرز عندما تقوم بتحويل جملة طويلة إلى نوعPChar. بصورة أساسية، المشكلة هي أنه بعد هذا التحويل، ستكون مسؤولا عن الجملة و عن محتواها، و لن تساعدك دلفي بشيء. لاحظ التغيير المحدود التالي لجزء توليف البرنامج الأول أعلاه، Button1Click:

procedure TForm1.Button2Click(Sender: TObject);
var
  S1: String;
begin
  SetLength (S1, 100);
  GetWindowText (Handle, PChar (S1), Length (S1));
  S1 := S1 + ' is the title'; // this won't work
  Button1.Caption := S1;
end;

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

كبف يمكننا تجاوز هذه المشكلة؟ الحلّ هو في اخبار النظام بأن يقوم باعادة تحويل الجملة المرتجعة من استدعاء GetWindowText الى جملة باسكال. عموما، إذا كتبت التوليف التالي:

S1 := String (S1);

فإن النظام سيتجاهله، لأن تحويل نوع بيانات إلى نفسه عملية غير مجدية. للحصول على جملة باسكال طويلة سليمة، تحتاج إلى إعادة تلبيس الجملة إلى نوع PChar ثم دع دلفي تقوم بالتحويل المناسب ثانية إلى جملة.

S1 := String (PChar (S1));

حقيقة، يمكن تخطّي عملية تحويل الجملة، لأن التحويلات من PChar إلى جملة تتم آليا في دلفي. ها هنا التوليف النهائي:

procedure TForm1.Button3Click(Sender: TObject);
var
  S1: String;
begin
  SetLength (S1, 100);
  GetWindowText (Handle, PChar (S1), Length (S1));
  S1 := String (PChar (S1));
  S1 := S1 + ' is the title';
  Button3.Caption := S1;
end;

بديل أخر و هو إعادة توصيف طول جملة دلفي، باستخدام طول جملة PChar، بكتابة:

SetLength (S1, StrLen (PChar (S1)));

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

تشكيل الجُمل

باستخدام علامة الموجب (+) و بعض وظائف التحويل (مثل IntToStr) يمكنك بالتأكيد بناء جمل مركبة من القيم الموجودة. عموما توجد عدة وجهات لتشكيل الأرقام، قيم العملة، و جمل أخرى إلى جملة النهائية. يمكنك استخدام وظيفة Format القوية أو واحدة من الوظائف المرافقة لها.

وظيفة Format تتطلب كمحدادات: النصّ الأساسي مع حواجز مكان placeholders (عادة ما تعلّم برمز %) و مصفوفة من القيم، كلّ قيمة خاصة بحاجز مكان. مثلا، لتشكيل رقمين في جملة يمكنك كتابة:

Format ('First %d, Second %d', [n1, n2]);

حيث n1 و n2 قيمتا عدد صحيح. الحاجز الأول يُستبدل بالقيمة الأولى، الثاني يوافق القيمة الثانية، و هكذا. إذا كان نوع المخرجات لحاجز (يشار إليه بحرف بعد رمز %) لا يوافق نوع المحدد ذو العلاقة، سيظهر خطأ وقت تشغيل. إن عدم وجود تفحص للنوع في وقت التجميع يشكل فعلا أكبر عيب في استخدام وظيفة Format.

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

بجانب استخدام %d، يمكنك استخدام حواجز أخرى معرّفة في هذه الوظيفة و التي تم سردها بايجاز في الجدول 7.1. توفّر هذه الحواجز مخرجات افتراضية حسب نوع البيانات. عموما يمكنك استخدام معيّنات تشكيل أخرى لتغيير المخرجات الافتراضية. معيّن العرض، مثلا، يحدد عددا ثابتا من الأحرف في المخرجات، بينما معين الدقّة يشير إلى عدد الخانات العشرية. مثلا،

Format ('%8d', [n1]);

يحوّل رقم n1 إلى جملة بثمانية أحرف، مع صفّ النص على اليمين (استخدم رمز سالب (-) لصف النصّ لليسار) مالئا الباقي بفراغات.

جدول 7.1: معينات النوع لوظيفة Format

معيّن النوع

الوصف

d (decimal) عشري، قيمة العدد الصحيح يحوّل إلى جملة من الخانات العشرية.
x (hexadecimal) ستعشري، قيمة الرقم الصحيح تُحوّل إلى جملة من الخانات الستعشرية.
p (pointer) مؤشّر، قيمة المؤشر يحوّل إلى جملة معبّر عنها بأعداد ستعشرية.
s (string) قيمة الجملة، الحرف، أو نوع PChar يتم نسخها في مخرجات الجملة.
e (exponential) مرفوع القوة، قيمة النقطة العائمة تحوّل إلى جملة مبنية على ترميز مرفوع القوة.
f (floating point) نقطة عائمة، قيمة النقطة العائمة تحوّل إلى جملة مبنية على ترميز النقطة العائمة.
g (general) عام، قيمة النقطة العائمة تحوّل إلى جملة عشرية بأقرب ما يمكن مستخدمة إما ترميز النقطة العائمة أو مرفوع القوة.
n (number) رقم، قيمة النقطة العائمة تحوّل إلى جملة نقطة عائمة لكنها أيضا تستخدم فواصل الآلاف.
m (money) نقود، قيمة النقطة العائمة تحوّل إلى جملة تمثل مقدار العملة. التحويل يعتمد على التوصيف الإقليمي لبيئة التشغيل- انظر ملف مساعدة دلفي تحت موضوع: Currency and date/time formatting variables.

أفضل طريقة لرؤية أمثلة عن هذه التحويلات هي في أن تقوم باختبار تشكيل الجمل بنفسك. لجعل هذا الأمر سهلا قمت بكتابة برنامج FmtTest، و الذي يسمح للمستخدم بتقديم جمل التشكيل للأرقام الصحيحة و أرقام النقطة العائمة. كما تراه في الشكل 7.2، هذا البرنامج يعرض نموذجا مقسما إلى جزئين، الجزء الأيسر للأرقام الصحيحة، و الجزء الأيمن لأرقام النقطة العائمة.

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

الشكل 7.2: مخرجات قيمة نقطة عائمة من برنامج FMTTest

يستخدم توليف هذا المثال نصوص متحكمات مختلفة لتوليد مخرجاته. هذا واحد من ثلاثة مسارات مرتبطة بزرّ Show:

procedure TFormFmtTest.BtnIntClick(Sender: TObject);
begin
  ShowMessage (Format (EditFmtInt.Text,
    [StrToInt (EditInt.Text)]));
  // if the item is not there, add it
  if ListBoxInt.Items.IndexOf (EditFmtInt.Text) < 0 then
    ListBoxInt.Items.Add (EditFmtInt.Text);
end;

التوليف أساسا يُجري عمليات تشكيل باستخدام نص خانة كتابة EditFmtInt و قيمة متحكم EditInt. إذا كانت جملة التشكيل غير موجودة في القائمة، يتم اضافتها عندئد. أما إذا لمس المستخدم بدلا من ذلك بندا في القائمة، فإن التوليف ينقل تلك القيمة إلى خانة الكتابة.

procedure TFormFmtTest.ListBoxIntClick(Sender: TObject);
begin
  EditFmtInt.Text := ListBoxInt.Items [
    ListBoxInt.ItemIndex];
end;

ملخّص

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

يتم مناولة الجمل في الذاكرة بطريقة حيوية خاصة، كما يحدث مع المصفوفات المفتوحة. هذا هو موضوع الفصل القادم.

الفصل التالي: الذاكرة

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