مقدمة إلى المصفوفات في دلفي

بسم الله الرحمن الرحيم

المصفوفات Arrays , تعتبر من أهم بنى البيانات (Data structures) , و تعتبر أيضاً البنية الأساسية لبقية بنى البيانات (مثل Stack و Queue ..) .

والمصفوفة هي مجموعة من العناصر (البيانات أو المتغيرات) من نفس النوع (مصفوفة متغيرات integer , مصفوفة متغيرات char ..) .

أنواع المصفوفات في دلفي :

هناك نوعان أساسيان من المصفوفات في دلفي :

1- مصفوفات ساكنة Static arrays : وهي التي يكون عددها معروفاً ومحدداً عند تصميم البرنامج أو كتابته , وهذا النوع من المصفوفات لا يمكن تغيير عدد عناصره أثناء تنفيذ البرنامج .

مثلاً مصفوفة تحتوي على درجات 5 طلاب , فهي من النوع الساكن لأن عناصرها محددة مسبقاً ومعروفة .

ويمكن تعريف المصفوفة الساكنة على الشكل التالي :

var ArrayName : array [ElementsNumber] of DataType ;

فمثلاً المصفوفة التالية :

var Marks : array [0..4] of integer

هي مصفوفة اسمها Marks , وعدد عناصرها 5 (من 0 إلى 4) , وعناصرها من النوع الرقمي Integer .

وبعد تعريف المصفوفة , يمكن تعيين قيم عناصرها بالترتيب أو على نحو منفصل

فيمكن مثلاً تعيين درجات الطلاب بالكود التالي :

var i : integer ;
begin
for i := 0 to 4 do
Marks[i] := i ;

في السطر الأول بدأنا حلقة for..do والتي تبدأ من 0 إلى 4 وقيمتها تكون في المتغير i .

في السطر الثاني نعين قيمة الدورة (i) إلى العنصر ذو الترتيب i من المصفوفة Marks .

هنا سيحصل كل عنصر على قيمة = ترتيبه في المصفوفة , فالعنصر الأول (ترتيبه 0) يحصل على القيمة 0 , والعنصر الثاني (ترتيبه 1) يحصل على القيمة 1 وهكذا ..

ملاحظة : يفضل استعمال الدالتين Low و High لتعيين رقم بداية ونهاية حلقة for..do عند التعامل مع المصفوفات , فيصبح الكود :

for i := Low(Marks) to High(Marks) do

حيث تقوم الدالة Low بإرجاع أدنى ترتيب داخل لمصفوفة الممررة إليها , وترجع الدالة High أقصى ترتيب داخل لمصفوفة الممررة إليها .

ومن خلال الكود التالي , يمكن عرض عناصر المصفوفة (في تطبيق Console) :

for i := Low(Marks) to High(Marks) do
WriteLn(Marks[i]) ;
ReadLn ;

من خلال حلقة for..do السابقة , سيتم عرض عناصر المصفوفة بالترتيب في نافذة Console .

يمكن أيضا تعيين قيم عناصر المصفوفة بشكل منفصل على النحو التالي :

Marks[0] := 0 ;
Marks[1] := 1 ;
Marks[2] := 2 ;
...

لكن يلاحظ على هذه الطريقة أنها طويلة (في المصفوفات التي تزيد عناصرها عن 100 عنصر مثلاً) , وأنها متعبة وتستغرق وقتاً طويلاً .

ويصبح الكود النهائي لتطبيق المصفوفات الساكنة :

var
Marks : array[0..4] of integer ;
i : integer ;
begin
try
for i := Low(Marks) to High(Marks) do
Marks[i] := i ;
for i := Low(Marks) to High(Marks) do
WriteLn(Marks[i]) ;
ReadLn ;
except
on E:Exception do
Writeln(E.Classname, ': ', E.Message);
end;
end.

———————————————-

2- المصفوفات الديناميكة Dynamic Arrays : وهي المصفوفات التي يكون عدد عناصرها مجهولاً عند تصميم أو كتابة البرنامج , ويمكن تعيين عدد عناصرها أثناء تنفيذ البرنامج .

فمثلاً مصفوفة مخصصة لتخزين درجات الطلاب , لكن عدد الطلاب مجهول عند كتابة البرنامج (بالنسبة للمبرمج) , ولكنه معروف للمستخدم عندها يجب استخدام مصفوفة ديناميكية لتمكين المستخدم من تعيين عناصرها أثناء استخدامه للبرنامج .

وتعريف المصفوفة الديناميكية مشابه لتعريف المصفوفة الساكنة , باستثناء تعيين عدد العناصر (لأنه مجهول أصلاً !!) , ويكون على الشكل التالي :

var Marks : array of integer ;

ويمكن تعيين عناصرها من خلال دالة SetLength على النحو التالي :

SetLength(Array, NewLength) ;

فمن خلال الأمر التالي (في تطبيق Console) يمكن تعيين عناصر المصفوفة الديناميكية Marks من خلال إدخال المستخدم :

var
L: Integer ;
begin
ReadLn(L) ;
SetLength(Marks, L) ;

ويمكن تعيين عناصر المصفوفة أيضاً من خلال حلقة (for..do مثلاً) أو بطريقة مفصلة

فيصبح الكود النهائي لتطبيق المصفوفات الديناميكية على الشكل التالي :

var
Marks : array of integer ;
i, L : integer ;
begin
try
WriteLn('Insert the new length :') ;
ReadLn(L) ;
SetLength(Marks, L) ;
for i := Low(Marks) to High(Marks) do
Marks[i] := i ;
for i := Low(Marks) to High(Marks) do
WriteLn(Marks[i]) ;
ReadLn ;
except
on E:Exception do
Writeln(E.Classname, ': ', E.Message);
end;
end.

ملاحظة : يبدأ ترتيب المصفوفة الديناميكية من الصفر .

الأمثلة :

يمكنك تحميل المثالين المشروحين في هذا الدرس من خلال الرابط التالي :

http://www.4shared.com/file/118918028/dfed408d/Arrays_lesson.html

البرمجة غرضية التوجه في دلفي

بسم الله الرحمن الرحيم

—————-

مقدمة …

اليوم ان شاء الله تعالى سوف أقوم بشرح وتطبيق المفاهيم الأساسية للبرمجة الكائنية في دلفي , وسأحاول عم تطبيق بسيط فيه بعض مزايا هذه التقنية التي اصبحت من اهم ما يجب على المبرمج معرفته , خصوصاً لو كان مبرمج للغة رائعة مثل دلفي …

—————-

مقدمة إلى OOP …

OOP وتعني Object Oriented Programming (أي البرمجة المسيرة بالكائنات) , وهي مختلفة عن النوع الآخر من البرمجة والذي يسمى بالبرمجة الإجرائية

ما هذه المقدمة المعقدة يا علاء ؟!

1- البرمجة الإجرائية :

عندما تريد عمل برنامج بلغة فجوال بيزك 6 مثلا , فانك تستخدم الدوال والإجراءات , وتناديها عند الحاجة لأداء عملية معينة

2- البرمجة الكائنية :

هنا انت تستخدم الكائنات , وتنادي على الوظائف الخاصة بهذه الكائنات للقيام بالعمليات التي تريدها .

والجزء الأساسي في البرمجة الكائنية هو الوحدة Class والتي تمثل الكائن … لنأخذ المثال التالي :

– نفرض ان لدينا كائن (أو وحدة) اسمه إنسان .

– وهذا الكائن لديه خصائص , مثل الطول والاسم والعمل ..الخ .

– ولديه وظائف وطرق يمكنه تأديتها , مثل الأكل والشرب والنوم ..الخ .

وحتى تنفذ عملية معينة موجود في هذا الكائن عليك النداء عليها من خلال هذا الكائن , كقولك في الحياة اليومية :

“أرجو أن تسقني كوب ماء يا محمد” .. هنا الكائن هو محمد , والوظيفة المستخدمة هي طلب الشرب .

هذا بصورة عامة وسريعة , ولتوضيح الصورة في الذهن أكثر

—————-

المفهوم الأول : الوراثة …

في الحياة اليومية , عندما يتوفى احدهم ويترك مالاً مثلاً .. فان هذا المال كان من خصائصة (Man.Money := 50000) وأورثها إلى كائن آخر … هذا تقريبا هو مفهوم الوراثة في البرمجة الكائنية , أي ان يعطي الكائن الأب (Parent) خصائصه ووظائفه إلى كائن آخر ,,, لكن هنا لن يتوفى كائن ان شاء الله 😛 (الا لو انت استعملت عليه دالة الهدم , وسأتطرق لها فيما بعد إن شاء الله تعالى) .

– مثال :

نعود الى الكائن إنسان السابق (لاحظ انه كائن عام فيه كل الخصائص) , وسنقوم بتوريث كائن اخر وهو “علاء” هذه الخصائص من الكائن الأب .

اذا سيصبح للكائن علاء خاصية الاكل والشرب والنوم ..الخ

—————-

المفهوم الثاني : الكبسلة …

لو كان لديك سر برمجي ما , وتتفوق به في برامجك (حركة في الواجهة أو غيرها…) , فعندما تقوم بعمل برنامج واعطائه للزبون فلن يهتم كيف قمت بعمل هذه الحركة , وإنما فقط كيف يستخدمها … وهذا ما يعرف بالإخفاء أو الكبسلة , أي انك تخفي الكود وتترك فقط طريقة استعماله

—————-

المفهوم الثالث : تعدد الأشكال …

نفترض ان هناك كائنين هما “العامل أ” و “العامل ب” , ولكل منهما نفس الخصائص والطرق .. ولدى كل منهم أسلوب اسمه GetIt , لا ستخراج ناتج معين

لكن العامل أ , يستعمل الاسلوب GetIt لضرب ثلاثة أعداد واخراج الناتج , بينما العامل ب يستخدمه لجمع ثلاثة أعداد واخراج المتوسط , وهذا ما يعرف بتعدد الأشكال , أي ان يكون هناك نفس الاسلوب لكلا الكائنين لكنه يؤدي وظيفة مختلفة

—————-

المفهوم الرابع : التجريد …

ويقصد به انك لا تلتفت إلى النقاط الصغيرة في البرنامج , وتشغل نفسك بها مما يعطلك ويعطل البرنامج وقد يظهر لك عديد من المشاكل التي كنت في غنى عنها .

مثلاً لو انك تقوم بعمل برنامج لادخال البيانات الخاصة بسيارة ما , فلست في حاجة إلى حساب عدد أولاد صاحب السيارة , إنما تركز على اشياء اخرى مثل لوحة السيارة , لونها , الشركة المصنعة ..الخ

—————-

المفهوم الخامس : البناء والهدم …

هي دالتين موجودتين داخل كل كائن :

دالة بناء : لإنشاء الكائن , وحجز مكانه في الذاكرة . وهي دالة Create

دالة هدم : لإزالة الكائن من الذاكرة ومن البرنامج . وهي دالة Free

—————-

التطبيق …

بعد هذه المقدمة والشرح , نبدأ مرحلة التطبيق لعمل برنامج بسيط بالبرمجة الكائنية

—————-

إنشاء وحدة في دلفي …

كما ذكرت سابقاً .. الوحدة في البرمجة الكائنية , هي مثل الخلية في جسم الانسان … فهي وحدة التركيب والوظيفة في بنية البرنامج , لانها تحتوي على الكائنات التي من اجلها سميت OOP بهذا الاسم …

لإنشاء وحدة في دلفي , وبكل بساطة اتبع الخطوات التالية :

– عادة , نضع الكائنات (او كل كائن) في وحدة منفصلة مع خصائصه ووظائفه .. حتى لا يتداخل مع بقية الكود وانما يكتفى بندائه من خلال الوحدة الأساسية ..

لذا ستكون الخطوة الأولى هي إنشاء وحدة جديدة (File –> New –> Unit)

طبعا , سنعمل على Console Application , لأننا لا نحتاج الشغل المرئي هنا …

– الان سنكتب الكود … لكن مهلا ! كود ماذا ؟ لم نحدد بعد ما سنفعل

سنقوم بعمل وحدة اسمها Animal ونتعامل مع هذا الكائن الحيوان 😛 … كمثال بسيط جدا

– سنقوم بكتابة الكود التالي في الوحدة الجديدة :
unit Unit1;
//-
interface
//-
type
Animal = class
//-
Name : String ;
Age : Byte ;
LiveIn : String ;
//-
procedure Speak ;
end;
//-
implementation
//-
{ Animal }
//-
procedure Animal.Speak;
begin
WriteLn('Name : ' + Self.Name) ;
WriteLn(Self.Age ) ;
end;
//-
end.

//———-

نشرح الكود :

Type : وتستخدم للاعلان عن نوع جديد , سجل أو وحدة .

Animal = class : وهنا ننشئ كائن جديد من نوع وحدة .

– بقية الكود متغيرات عادية تمثل خصائص الكائن .

procedure Speak : وظيفة خاصة بالكائن .

Self : تشير إلى الكائن المستعمل , أي انها تشير إلى الكائن الذي تمت مناداة الوظيفة من خلاله

—————-

استعمال الكائن …

نعود الان إلى الوحدة الأساسية لنستعمل الكائن , ونكتب في الوحدة الرئيسية :

program Project1;
//-
{$APPTYPE CONSOLE}
//-
uses
SysUtils,
Unit1 in 'Unit1.pas';
//-
var
Cat : Animal ;
//-
begin
Cat := Animal.Create ;
//-
with Cat do
begin
Name := 'ShoSho' ;
Age  := 3 ;
LiveIn := 'My home' ;
end ;
//-
Cat.Speak ;
//-
Cat.Free ;
//-
ReadLn ;
end.

//———-

نشرح الكود :

Cat : Animal : عرفنا متغير من نوع الكائن الذي أنشأناه للتو .

Cat := Animal.Creat : أنشأنا الكائن ووضعناه داخل المتغير .

– الست أسطر التالية لضبط خصائص الكائن .

Cat.Spreak : استدعاء الإجراء الخاص بعرض قيم خصائص الكائن .

Cat.Free : تفريغ الذاكرة من الكائن بعد استعماله .

—————-

انشاء ابن للكائن Animal …

سننشئ الآن كائناً اسمه LittleAnimal وسيكون ابنا للكائن Animal , وهذا باستخدام الوراثة …

– أضف الكود التالي بعد كلمة “end;” الأولى في الوحدة الخاصة بالكائن :

LittleAnimal = class(Animal)
end ;

//———-

نشرح الكود :

– مرننا اسم الاب الى دالة class , حتى يتم استدعاء خصائص الاب الى الابن

يجب ان نعدل على كود الوحدة الاولى الرئيسية , فيصبح :

program Project1;
//-
{$APPTYPE CONSOLE}
//-
uses
SysUtils,
Unit1 in 'Unit1.pas';
//-
var
Cat : Animal ;
Kitty : LittleAnimal ;
//-
begin
Cat := Animal.Create ;
Kitty := LittleAnimal.Create ;
//-
with Cat do
begin
Name := 'ShoSho' ;
Age  := 3 ;
LiveIn := 'My home' ;
end ;
//-
with Kitty do
begin
Name := 'SeSe' ;
Age  := 1 ;
LiveIn := 'My home' ;
end;
//-
Cat.Speak ;
Kitty.Speak ;
//-
Cat.Free ;
Kitty.Free ;
//-
ReadLn ;
end.

—————-

ملاحظات …

1- لم نهتم بأشياء أخرى غير أساسية كلون الحيوان , أو هل لديه شعر أم لا (التجريد) .

2- وفرنا الوقت باستخدام الوراثة , وستعرف هذا عندما تكتب كائن له كثير من الخصائص , ومن ثم تورثه إلى آخر (الوراثة) .

3- لم نطلع المستخدم على كود إجراء Speak (الكبسلة) .

—————-

أرجو ان يكون الدرس قد افادكم ..

المؤشرات

بسم الله الرحمن الرحيم

—————–

أخيراً يا علاء ستضع درس كويس … ؟

بدلاً من المقالات التي مللنا منها … 😛

—————–

مقدمة …

دلفي لغة قوية (معروفة !) , وتحتوي على العديد من المصطلحات الجديدة والتي لم تكن موجودة في VB , والتي يعتبرها البعض “لغة برمجة قوية” !!! 😛

—————–

ما هي المؤشرات … ؟

المؤشرات , من اسمها تعني انها تشير إلى شيء ما . هذا الشيء هو عنوان في الذاكرة

صعبة ؟ … ابسطهالك :

نفرض أنك تسكن في شقة , هذه الشقة في المدينة . وهذه الشقة لها عنوان , وانت قمت بوضع العنوان على بطاقة , ووزعته على حبايبك

في هذا المثال : المدينة هي RAM , الشقة هي المكان المحجوز في الذاكرة , انت هو القيمة المخزنة في هذا المكان , عنوان الشقة هنا هو عنوان المكان المخزن فيه القيمة في الذاكرة , البطاقة هنا هي المؤشر الذي نتحدث عنه …

صعبة ؟ … ابسطهالك :

شرح الصورة السابقة :

المربع الأصفر الكبير = ذاكرة الجهاز RAM .

المربعات الحمراء الصغيرة = أماكن تخزين البيانات داخل الذاكرة .

الدائرة الحمراء = هي بيانات مخزنة داخل الذاكرة (وليكن القيمة الرقمية 12345) .

المربع الأخضر = عنوان مكان تخزين القيمة .

المربع الأزرق = المؤشر … ويحتوي على قيمة هي عنوان مخزن البيانات .

—————–

فوائد المؤشرات …

استخدام المؤشرات يزيد سرعة برنامجك , لأنها توفر لك وصولاً سريعاً ومباشراً إلى ذاكرة الجهاز .

كما انها توفر إمكانات مميزة , وتوفر عليك الكثير الكثير من الوقت (خصوصاً في دلفي) .

كما أنها من المهارات الأساسية للمبرمجين المحترفين , والتي يجب ان يكون المبرمج على دراية وإحاطة جيدة بها , لأنها تعتبر من مميزات وأساسيات لغة دلفي .

—————–

أنواع المؤشرات …

المؤشرات نوعان :

النوع الأول : Typed Pointers … وهي المؤشرات التي تستخدم مع أنواع (Types) معينة , أو نوع بيانات معين .

النوع الثاني : Untyped Pointers … وهي أكثر شمولاً , وتستخدم مع أي نوع من أنواع البيانات .

—————–

تعريف المؤشرات …

وهذا في قمة قمة السهولة , فقط ضع الرمز “^” قبل نوع البيانات .

مثلاً : لتعريف متغير من النوع الرقمي نكتب :
Var
I : Integer ;

لكن لتعريف مؤشر Typed نكتب :

Var
P : ^Integer ;

يااااااه , دي سهلة خالص !

والآن , سيمكنك استخدام المؤشر P للإشارة إلى أي مكان في الذاكرة . شرط أن يحتوي هذا المكان على قيمة من النوع Integer فقط (طبعاً , لأننا عرفنا مؤشر من النوع Typed) .

لكن توقف , لا تحاول استخدام المؤشر P , فنحن لم ننته من بعد

وهذا لأننا لم نقم بتعيين قيمة له ! فماذا تتوقع من مؤشر أن يعطيك , وأنت لم تقم بتعيين قيمته بعد ؟! بالطبع سيشير إلى قيمة خاطئة , أو عنوان خاطئ في الذاكرة .

وهذا مثل انك تسأل شخص عن الطريق , وهو ليس لديه خريطة أو خلفية عن هذا الطريق . وبالتالي سيعطيك معلومات خاطئة .

لتعيين قيمة مؤشر , فهذا أمر في قمة قمة السهولة , فكل ما عليك هو كتابة رمز @ قبل اسم المتغير الذي تريد من المؤشر أن يشير إلى مكانه في الذاكرة .

فمثلاً , لنفرض أنك قمت بتعريف المتغير i السابق , والمؤشر P أيضاً

الكود التالي لربط المتغير بالمؤشر :

P := @I ;

ولعرض قيمة المؤشر , نكتب الكود التالي (في Console Application)

WriteLn(Integer(P)) ;

الآن يصبح الكود حتى الآن هو :

Var
I : Integer ;
P : ^Integer ;
Begin
I := 100 ; // تعيين قيمة المتغير
WriteLn(I) ;
P := @I ;
WriteLn(Integer(P)) ;
ReadLn ; // هذه الكلمة تتيح لك فرصة قراءة المكتوب في نافذة التطبيق

—————–

تغيير قيمة المتغير من خلال المؤشر …

بعدما عرفنا المؤشر , وجعلناه يعرض عنوان متغير ما في الذاكرة . سنقوم بتغيير قيمة المتغير i من خلال المؤشر P

ولفعل ذلك , نضع الرمز “^” بعد اسم المؤشر , عند القيام بعملية تعيين القيمة .

فنضع الكود التالي بعد السطر الخاص بربط المتغير بالمؤشر :

P^ := 200 ;
WriteLn(I) ;

فيصبح الكود :

Var
I : Integer ;
P : ^Integer ;
Begin
I := 100 ; // تعيين قيمة المتغير
WriteLn(I) ;
P := @I ;
P^ := 200 ;
WriteLn(I) ;
ReadLn ; // هذه الكلمة تتيح لك فرصة قراءة المكتوب في نافذة التطبيق

لاحظ الآن ان البرنامج سيعرض القيمتين 100 ثم 200 .

جرب مثلاً استبدال i في السطر قبل الأخير في الكود السابق , وضع بدلاً منها P ثم رمز “^” ولاحظ النتيجة … 200 .

—————–

المؤشرات من النوع Untyped …

الجزئان السابقان تحدثا عن المؤشرات من نوع Typed , لكن الآن لنتحدث عن المؤشرات Untyped , والتي تشير إلى أي نوع بيانات (كما ذكرت في البداية)

هناك فرقان أساسيان من ناحية كتابة الكود , بين المؤشرات Typed و Untyped :

الفرق الأول :

لتعريف مؤشر Typed نكتب :

P : ^DataType ;

ونضع نوع البيانات المطلوب

لكن لتعريف مؤشر Untyped نكتب :

P : Pointer ;

الفرق الثاني :

لزيادة قيمة متغير من خلال مؤشر Typed نكتب :

Inc(P^) ;

لكن لزيادة قيمة متغير من خلال مؤشر Untyped نكتب :

Inc(PDataType(P)^) ;

حيث أن PDataType قد تكون PString , PChar , PInteger … إلخ , المهم ان تضع P قبل اسم نوع البيانات

ملحوظة هامة : حرف P في كلمة PDataType لايشير إلى المؤشر P , إنما يوضع لدلالة على ان نوع البيانات سيشار إليه بالمؤشر P . أي انه إذا كان اسم المؤشر هو MySuperPointer ومن النوع Integer فنكتب :

Inc(PInteger(MySuperPointer)^) ;

وبهذا , يصبح الكود الخاص بالمؤشرات Untyped هو :
Var
Int : Integer ;
Pon : Pointer ;
Begin
Int := 2007 ;
WriteLn(Int) ;
Pon := @Int ;
Inc(PInteger(Pon)^) ;
WriteLn('Now Int = ' , Int) ;

—————–

تحرير المؤشر …

كل ما تفعله هو وضع القيمة الخالية nil في المؤشر عن طريق الكود التالي :

P := nil ;

وهذا لتحريره , وجعله لايشير إلى أي متغير .

—————–

طبعا المؤشرات ليست بهذه البساطة , ولا بهذا المستوى

هناك العديد من الاستخدامات والأفكار والطرق وغيرها …

يمكنك الاستفادة من المصادر التالية :

http://www.delphibasics.co.uk/Article.asp?Name=Pointers

http://en.wikipedia.org/wiki/Pointers

أيضاً كتاب Inside Delphi 2006

—————–

ملاحظتان ختاميتان :

الأولى : الكود النهائي لهذا الشرح هو :

var
I : integer ;  // تعريف متغير رقمي
P : ^integer ; // تعريف المؤشر
Int : integer ;
Pon : Pointer ; // Untyped تعريف مؤشر من النوع
begin
WriteLn('1- Typed Pointers') ;
I := 100 ; // تعيين قيمة المتغير
WriteLn('I = ' ,I) ; // عرض قيمة المتغير
P := @I ; // ربط المتغير بالمؤشر
P^ := 200 ; // تغيير قيمة المتغير من خلال المؤشر
WriteLn('Now I = ' ,P^) ; // عرض قيمة المتغير بعد التعديل
//-------------------------------------
WriteLn('') ;
WriteLn('Untyped Pointers') ;
Int := 2007 ;
WriteLn('Int = ' , Int) ;
Pon := @Int ;
Inc(PInteger(Pon)^) ; // Pon زيادة قيمة المتغير الذي يشير إليه المؤشر
WriteLn('Now Int = ' , Int) ;
ReadLn ; // ترك فرصة لقراءة ما قمنا بطباعته على النافذة
end.

الثانية : ان الشرح مترجم من كتاب Inside Delphi 2006 , لكن ليس حرفياً , وإنما اخترت أجزاء معينة , وعرضتها بأسلوبي وأضفت عدة أشياء أخرى