مقدمة إلى البرمجة ولغة C++
يستمر هذا البرنامج التعليمي على الإنترنت مع المزيد من المفاهيم المتقدمة، لذا يُرجى قراءة الجزء الثالث. سنركّز في هذه الوحدة على استخدام المؤشرات والبدء باستخدام الكائنات.
التعلُّم بالمثال الثاني
ينصب تركيزنا في هذه الوحدة على اكتساب المزيد من التدريب على التحلل وفهم المؤشرات، والبدء في استخدام الكائنات والفئات. اعمل من خلال الأمثلة التالية. اكتب البرامج بنفسك عندما يُطلب منك ذلك، أو أجرِ التجارب. لا يمكننا التأكيد بما يكفي على أنّ التدريب هو مفتاح أن تصبح مبرمِجًا جيدًا.
المثال 1: المزيد من التمارين على التحليل
راجِع الإخراج التالي من لعبة بسيطة:
Welcome to Artillery. You are in the middle of a war and being charged by thousands of enemies. You have one cannon, which you can shoot at any angle. You only have 10 cannonballs for this target.. Let's begin... The enemy is 507 feet away!!! What angle? 25< You over shot by 445 What angle? 15 You over shot by 114 What angle? 10 You under shot by 82 What angle? 12 You under shot by 2 What angle? 12.01 You hit him!!! It took you 4 shots. You have killed 1 enemy. I see another one, are you ready? (Y/N) n You killed 1 of the enemy.
الملاحظة الأولى هي النص التمهيدي الذي يتم عرضه مرة واحدة لكل برنامج والتنفيذ. نحتاج إلى أداة إنشاء أرقام عشوائية لتحديد مسافة العدو لكل جولة. فنحن بحاجة إلى آلية للحصول على مدخلات الزاوية من المشغّل هو في هيكل دائري لأنه يتكرر إلى أن نصيب العدو. نحتاج أيضًا إلى دالة لاحتساب المسافة والزاوية. أخيرًا، ينبغي لنا أن نتتبع عدد الضربات التي أدّت إلى إصابة العدو، وكذلك عدد الأعداء لدينا التي تلقّاها أثناء تنفيذ البرنامج. في ما يلي مخطّط محتمل للبرنامج الرئيسي.
StartUp(); // This displays the introductory script. killed = 0; do { killed = Fire(); // Fire() contains the main loop of each round. cout << "I see another one, care to shoot again? (Y/N) " << endl; cin >> done; } while (done != 'n'); cout << "You killed " << killed << " of the enemy." << endl;
يعالج الإجراء Fire تشغيل اللعبة. في هذه الدالة، نستدعي منشئ أرقام عشوائية للحصول على مسافة العدو، ثم إعداد الحلقة الحصول على ملاحظات اللاعب واحتساب ما إذا كان قد ضرب العدو أم لا. تشير رسالة الأشكال البيانية شرط الحراسة في الحلقة هو مدى اقترابنا من ضرب العدو.
In case you are a little rusty on physics, here are the calculations: Velocity = 200.0; // initial velocity of 200 ft/sec Gravity = 32.2; // gravity for distance calculation // in_angle is the angle the player has entered, converted to radians. time_in_air = (2.0 * Velocity * sin(in_angle)) / Gravity; distance = round((Velocity * cos(in_angle)) * time_in_air);
بسبب الاستدعاءات إلى cos() وsin()، ستحتاج إلى تضمين match.h. تجربة كتابة هذا البرنامج - إنها ممارسة رائعة في تحليل المشكلات للغة C++ الأساسية. تذكر أن تقوم بمهمة واحدة فقط في كل دالة. هذه هي الذي كتبناه حتى الآن أكثر تطورًا، لذلك قد يستغرق الأمر قليلاً حان الوقت للقيام بذلك.إليك الحلّ الذي نقدّمه.
المثال 2: التدرّب باستخدام المؤشرات
هناك أربعة أشياء يجب تذكرها عند التعامل مع المؤشرات:- المؤشرات هي متغيّرات تحتفظ بعناوين الذاكرة. أثناء تنفيذ البرنامج،
يتم تخزين جميع المتغيرات في الذاكرة، وكل منها على عنوان أو موقع فريد خاص به.
المؤشر هو نوع خاص من المتغيرات التي تحتوي على عنوان ذاكرة بدلاً
من قيمة البيانات. تمامًا كما يتم تعديل البيانات عند استخدام متغيّر عادي،
يتم تعديل قيمة العنوان المخزّن في مؤشر عند التلاعب بمتغيّر المؤشر
. في ما يلي مثال:
int *intptr; // Declare a pointer that holds the address // of a memory location that can store an integer. // Note the use of * to indicate this is a pointer variable. intptr = new int; // Allocate memory for the integer. *intptr = 5; // Store 5 in the memory address stored in intptr.
- نقول عادةً أنّ المؤشر "يشير" إلى الموقع الذي يخزِّنه
(أي "العنصر المشار إليه"). إذًا، في المثال أعلاه، نقاط intptr إلى المؤشر
5-
لاحظ استخدام لتخصيص ذاكرة لعددنا الصحيح مدبّب. هذا إجراء يجب علينا القيام به قبل محاولة الوصول إلى الموجّه.
int *ptr; // Declare integer pointer. ptr = new int; // Allocate some memory for the integer. *ptr = 5; // Dereference to initialize the pointee. *ptr = *ptr + 1; // We are dereferencing ptr in order // to add one to the value stored // at the ptr address.
يستخدم العامل * لإلغاء تحديد المصدر في C. أحد الأخطاء الأكثر شيوعًا ينجح مبرمجو C/C++ في العمل باستخدام المؤشرات عندما تنسى تهيئة والموجّه. قد يتسبب هذا أحيانًا في حدوث عطل في بيئة التشغيل لأننا نقوم بالوصول إلى يشير ذلك المصطلح إلى موقع جغرافي في الذاكرة يحتوي على بيانات غير معروفة. إذا حاولنا تعديل هذه البيانات، يمكن أن يؤدي ذلك إلى تلف خفيف في الذاكرة، ما يجعل من الصعب العثور على الخطأ.
- يؤدي تعيين المؤشر بين مؤشرين إلى جعلهما يشيران إلى نفس المؤشر.
وبالتالي، فإنّ عملية الربط y = x; تجعل y تشير إلى العنصر نفسه الذي يشير إليه x. تعيين المؤشر
لا يؤثر في العنصر المشار إليه. يؤدي هذا الإجراء إلى تغيير مؤشر واحد فقط ليحصل على الموقع نفسه.
كمؤشر آخر. بعد تعيين المؤشرَين، "يتشاركان"
المُشار إليه.
void main() { int* x; // Allocate the pointers x and y int* y; // (but not the pointees). x = new int; // Allocate an int pointee and set x to point to it. *x = 42; // Dereference x and store 42 in its pointee *y = 13; // CRASH -- y does not have a pointee yet y = x; // Pointer assignment sets y to point to x's pointee *y = 13; // Dereference y to store 13 in its (shared) pointee }
في ما يلي تتبع لهذه التعليمة البرمجية:
1. خصص مؤشرين x وy. يؤدي تخصيص المؤشرات وليس تخصيص أيّ نقاط توجيه | ![]() |
2. يمكنك تخصيص أداة توجيهية وضبط x للإشارة إليها. | ![]() |
3- يجب إدخال الرمز x لتخزين 42 في الموجِّه. هذا مثال أساسي لعملية إلغاء الإشارة. ابدأ عند x، واتّبِع السهم للوصول إلى: بوينته. | ![]() |
4. حاوِل الإشارة إلى y لتخزين الرقم 13 في أداة التوجيه. يتعطّل هذا بسبب حدوث عطل. ليس لدي مُوجه -- لم يتم تعيينه أبدًا. | ![]() |
5- اضبط y = x؛ لكي تشير y إلى العنصر الذي يشير إليه x. تشير الآن x و y إلى نفس الموجه -- إنهم "يشاركون". | ![]() |
6- حاوِل الإشارة إلى y لتخزين الرقم 13 في أداة التوجيه. ينجح هذا الإجراء هذه المرة، لأنّ عملية الربط السابقة منحت y عنصرًا يتم الإشارة إليه. | ![]() |
كما ترى، الصور مفيدة جدًا في فهم استخدام المؤشر. في ما يلي مثال آخر:
int my_int = 46; // Declare a normal integer variable. // Set it to equal 46. // Declare a pointer and make it point to the variable my_int // by using the address-of operator. int *my_pointer = &my_int; cout << my_int << endl; // Displays 46. *my_pointer = 107; // Derefence and modify the variable. cout << my_int << endl; // Displays 107. cout << *my_pointer << endl; // Also 107.
لاحظ في هذا المثال أنّنا لم نخصّص ذاكرة مطلقًا باستخدام عامل التشغيل new. أعلنا عن متغير عدد صحيح عادي وعالجناه باستخدام المؤشرات.
في هذا المثال، نوضح استخدام عامل الحذف الذي يؤدي إلى إلغاء تخصيص لأجزاء من الذاكرة، وكيف يمكننا تخصيص بُنى أكثر تعقيدًا. سنتناول تنظيم الذاكرة (حزمة لأجزاء من الذاكرة وأخرى وقت التشغيل) في درس آخر. في الوقت الحالي، يمكن اعتبار كومة الذاكرة المؤقتة بمثابة مخزن فارغ للذاكرة متاحة للبرامج قيد التشغيل.
int *ptr1; // Declare a pointer to int. ptr1 = new int; // Reserve storage and point to it. float *ptr2 = new float; // Do it all in one statement. delete ptr1; // Free the storage. delete ptr2;
في هذا المثال الأخير، نوضّح كيفية استخدام المؤشرات لتمرير القيم بالرجوع إلى دالة. هذه هي الطريقة التي نعدِّل بها قيم المتغيرات داخل الدالة.
// Passing parameters by reference. #include <iostream> using namespace std; void Duplicate(int& a, int& b, int& c) { a *= 2; b *= 2; c *= 2; } int main() { int x = 1, y = 3, z = 7; Duplicate(x, y, z); // The following outputs: x=2, y=6, z=14. cout << "x="<< x << ", y="<< y << ", z="<< z; return 0; }
إذا أردنا إزالة علامة & من الوسيطات في تعريف الدالة Duplicate، سنُمرِّر المتغيّرات "حسب القيمة"، أي أنّه يتم إنشاء نسخة من قيمة المتغيّر. تؤدي أي تغييرات يتم إجراؤها على المتغيّر في الدالة إلى تعديل النسخة. ولا تقوم بتعديل المتغير الأصلي.
فعند تمرير متغير بالمرجع، لا نمرر نسخة من قيمته، نُمرِّر عنوان المتغير إلى الدالة. إنّ أي تعديل نجريه على المتغيّر المحلي يُعدّل في الواقع المتغيّر الأصلي الذي تم تمريره.
إذا كنت من مبرمجي C، هذه ميزة جديدة. يمكننا إجراء الإجراء نفسه في C من خلال تحديد Duplicate() على أنّه Duplicate(int *x)، وفي هذه الحالة، يكون x مؤشرًا إلى int، ثمّ استدعاء Duplicate() مع الوسيطة &x (عنوان x)، واستخدام إلغاء الإشارة إلى x ضمن Duplicate() (راجِع ما يلي). ولكنّ C++ توفّر طريقة أبسط لنقل القيم إلى الدوالّ باستخدام المرجع، على الرغم من أنّ طريقة C القديمة لا تزال تعمل.
void Duplicate(int *a, int *b, int *c) { *a *= 2; *b *= 2; *c *= 2; } int main() { int x = 1, y = 3, z = 7; Duplicate(&x, &y, &z); // The following outputs: x=2, y=6, z=14. cout << "x=" << x << ", y=" << y << ", z=" << z; return 0; }
يُرجى العلم أنّه في مراجع C++، لا نحتاج إلى تمرير عنوان متغيّر، ولا نحتاج إلى إلغاء الإشارة إلى المتغيّر داخل الدالة التي يتمّ استدعاؤها.
ما هو ناتج البرنامج التالي؟ ارسم صورة للذاكرة لمعرفة ذلك.
void DoIt(int &foo, int goo); int main() { int *foo, *goo; foo = new int; *foo = 1; goo = new int; *goo = 3; *foo = *goo + 3; foo = goo; *goo = 5; *foo = *goo + *foo; DoIt(*foo, *goo); cout << (*foo) << endl; } void DoIt(int &foo, int goo) { foo = goo + 3; goo = foo + 4; foo = goo + 3; goo = foo; }
شغّل البرنامج لمعرفة ما إذا كنت قد حصلت على الإجابة الصحيحة.
المثال الثالث: تمرير القيم حسب المرجع
اكتب دالة تسمى accelerate() تستخدم كمدخل لسرعة السيارة ومقدار ما. تضيف الدالة الكمية إلى السرعة لتسريع المركبة. يجب تمرير مَعلمة السرعة بالإشارة، والمبلغ بالقيمة. إليك الحلّ الذي نقدّمه.
المثال 4: الفئات والكائنات
فكِّر في الصف التالي:
// time.cpp, Maggie Johnson // Description: A simple time class. #include <iostream> using namespace std; class Time { private: int hours_; int minutes_; int seconds_; public: void set(int h, int m, int s) {hours_ = h; minutes_ = m; seconds_ = s; return;} void increment(); void display(); }; void Time::increment() { seconds_++; minutes_ += seconds_/60; hours_ += minutes_/60; seconds_ %= 60; minutes_ %= 60; hours_ %= 24; return; } void Time::display() { cout << (hours_ % 12 ? hours_ % 12:12) << ':' << (minutes_ < 10 ? "0" :"") << minutes_ << ':' << (seconds_ < 10 ? "0" :"") << seconds_ << (hours_ < 12 ? " AM" : " PM") << endl; } int main() { Time timer; timer.set(23,59,58); for (int i = 0; i < 5; i++) { timer.increment(); timer.display(); cout << endl; } }
لاحظ أنّ متغيّرات أعضاء الفئة تحتوي على شرطة سفلية متبوعة. ويتم ذلك للتمييز بين المتغيّرات المحلية ومتغيّرات الفئة.
أضِف طريقة نقص إلى هذه الفئة. إليك الحلّ الذي نقترحه.
عجائب العلوم: علوم الكمبيوتر
تمارين
كما هو الحال في الوحدة الأولى من هذه الدورة التدريبية، لا نقدّم حلولاً للتمارين والمشاريع.
يُرجى العِلم أنّ البرنامج الجيد...
يتم تجزئتها منطقيًا إلى دوال تؤدي كلّ منها مهمة واحدة فقط.
... لديه برنامج رئيسي يشبه مخططًا تفصيليًا لما سيفعله البرنامج.
... تحتوي على أسماء دوال وثوابت ومتغيّرات وصفية.
... يستخدم الثوابت لتجنب أي "سحر" الأرقام في البرنامج.
... أن تتضمّن واجهة مستخدم سهلة الاستخدام
تمارين الإحماء
- التمرين 1
يمتلك العدد الصحيح 36 خاصية خاصة: إنه مربع مثالي مجموع الأعداد الصحيحة من 1 إلى 8. الرقم التالي من هذا النوع هو 1225 الذي يساوي 352، وهو مجموع الأعداد الصحيحة من 1 إلى 49. ابحث عن الرقم التالي الذي يكون مربّعًا كاملاً ويكون أيضًا مجموع سلسلة من 1 إلى n. هذا الرقم التالي أكبر من 32767. يمكنك استخدام دوال المكتبة التي تعرفها، (أو المعادلات الرياضية) لتشغيل برنامجك بشكل أسرع. من الممكن أيضًا لكتابة هذا البرنامج باستخدام التكرارات الحلقية لتحديد ما إذا كان الرقم مثاليًا مربع أو مجموع سلسلة. (ملاحظة: استنادًا إلى جهازك وبرنامجك، قد يستغرق العثور على هذا الرقم بعض الوقت).
- التمرين 2
يحتاج متجر الكتب في الكلية إلى مساعدتك في تقدير أرباحه للعام القادم. أثبتت التجربة أن المبيعات تعتمد إلى حد كبير على ما إذا كان يجب شراء الكتاب أم لا بالنسبة لدورة تدريبية أو اختيارية فقط، وما إذا تم استخدامها في الفصل أم لا من قبل. سيباع الكتاب المدرسي الجديد المطلوب بنسبة 90% من التسجيل المحتمل، ولكن إذا تم استخدامه في الفصل من قبل، فسيشتري 65٪ فقط. وبالمثل، سوف يشتري 40% من الطلاب المحتملين كتابًا مدرسيًا جديدًا اختياريًا، ولكن إذا استُخدِمت في الفئة قبل أن يشتري 20% فقط من الأشخاص. (يُرجى ملاحظة أنّ كلمة "مُستعمَل" هنا لا يعني الكتب المستعملة).
اكتب برنامجًا يقبل سلسلة من الكتب كمدخل (إلى أن يُدخِل المستخدم عنصرًا فاصلاً). بالنسبة إلى كل كتاب، اطلب ما يلي: رمز الكتاب، وتكلفة نسخة واحدة من الكتاب، والعدد الحالي للكتب المتوفّرة، وعدد الطلاب المحتملين في الصف، والبيانات التي تشير إلى ما إذا كان الكتاب مطلوبًا/اختياريًا، أو جديدًا/مُستخدَمًا في السابق. كنتيجة، يجب عرض كل معلومات الإدخال في شاشة منسَّقة بشكل جيد مع تحديد عدد الكتب التي يجب طلبها (إن توفّرت) مع الإشارة إلى أنّه يتم طلب الكتب الجديدة فقط، ويجب عرض التكلفة الإجمالية لكل طلب.
بعد اكتمال إدخال جميع البيانات، يمكنك عرض إجمالي تكلفة جميع طلبات الكتب، والأرباح المتوقّعة إذا دفع المتجر %80 من السعر المحدّد. بما أنّنا لم نتطرق بعد إلى أي طرق للتعامل مع مجموعة كبيرة من البيانات الواردة في أحد البرامج (ترقَّب مزيدًا من المعلومات)، ما عليك سوى معالجة كتاب واحد في كل مرة وعرض شاشة الإخراج لهذا الكتاب. وبعد ذلك، عندما ينتهي المستخدم من إدخال جميع البيانات، يجب أن يخرج برنامجك القيم الإجمالية والربحية.
قبل أن تبدأ في كتابة التعليمات البرمجية، خذ بعض الوقت للتفكير في تصميم هذا البرنامج. يمكنك تقسيمها إلى مجموعة من الدوالّ، وإنشاء دالة main() التي تبدو مثل مخطّط تفصيلي لحلّ المشكلة. تأكد من أن كل دالة تقوم بمهمة واحدة.
في ما يلي نموذج للمخرجات:
Please enter the book code: 1221 single copy price: 69.95 number on hand: 30 prospective enrollment: 150 1 for reqd/0 for optional: 1 1 for new/0 for used: 0 *************************************************** Book: 1221 Price: $69.95 Inventory: 30 Enrollment: 150 This book is required and used. *************************************************** Need to order: 67 Total Cost: $4686.65 *************************************************** Enter 1 to do another book, 0 to stop. 0 *************************************************** Total for all orders: $4686.65 Profit: $937.33 ***************************************************
مشروع قاعدة البيانات
في هذا المشروع، نقوم بإنشاء برنامج C++ كامل الوظائف يقوم بتنفيذ تطبيق قاعدة البيانات.
سيسمح لنا برنامجنا بإدارة قاعدة بيانات للملحّنين والمعلومات ذات الصلة عنها. تشمل ميزات البرنامج ما يلي:
- إمكانية إضافة مؤلف جديد
- إمكانية ترتيب المؤلف الموسيقي (أي الإشارة إلى مدى إعجابنا أو عدم إعجابنا بموسيقى المؤلف الموسيقي)
- إمكانية عرض جميع الملحّنين في قاعدة البيانات
- إمكانية عرض جميع الملحنين حسب الترتيب
"هناك طريقتان لإنشاء تصميم البرامج: إحدى الطرق هي تسهيل الأمر بحيث يكون هناك وعدم وجود أي أوجه قصور بها، والطريقة الأخرى هي جعل الأمر معقدًا للغاية بحيث هي عدم وجود أي قصور واضحة. الطريقة الأولى أكثر صعوبة بكثير". - C.A.R. Hoare
لقد تعلّم الكثير منا التصميم والترميز باستخدام نهج "الإجراءي". والسؤال الأساسي الذي نبدأ به هو "ما الذي يجب أن يفعله البرنامج؟". أر وتحلل حل مشكلة إلى مهام، وكل منها يحل جزءًا من المشكلة. يتم تعيين هذه المهام للدوال في برنامجنا والتي تسمى بشكل تسلسلي من main() أو من دوال أخرى. يعد هذا النهج خطوة بخطوة مثاليًا لبعض المشكلات التي نحتاج إلى حلها. ولكن في أغلب الأحيان، لا تكون البرامج تسلسلات المهام أو الأحداث.
باستخدام نهج كائني (OO)، نبدأ بالسؤال "ما من العالم الحقيقي الكائنات التي أقوم بتصميمها؟" وبدلاً من تقسيم برنامج إلى مهام كما هو موضح نقسمه إلى نماذج لأشياء مادية. وهذه الأشياء المادية لها حالة محددة من خلال مجموعة من السمات، ومجموعة من السلوكيات أو الإجراءات التي التي يمكنهم تنفيذها. قد تؤدي الإجراءات إلى تغيير حالة الكائن، أو قد تؤدي إلى تنفيذ إجراءات كائنات أخرى. الفرضية الأساسية هي أن الشيء "يعرف" كيف للقيام بالأشياء من تلقاء نفسها.
في تصميم OO، نعرّف الأشياء المادية من حيث الفئات والكائنات؛ السمات وسلوكياتهم. يوجد بشكل عام عدد كبير من الكائنات في برنامج OO. ومع ذلك، فإنّ العديد من هذه العناصر متشابهة بشكل أساسي. يُرجى مراعاة ما يلي:
في هذا الرسم التخطيطي، حددنا كائنين من فئة Apple. ولكل كائن السمات والإجراءات نفسها مثل الفئة، غير أن الكائن تحدد سمات نوع معين من التفاح. بالإضافة إلى ذلك، تستخدم الشبكة الإعلانية الإجراء يؤدي إلى عرض السمات لهذا الكائن بعينه، على سبيل المثال، "أخضر" و"Sour".
يتكون تصميم OO من مجموعة من الفئات، والبيانات المرتبطة بهذه الفئات، ومجموعة الإجراءات التي يمكن أن تؤديها الفئات. نحتاج أيضًا إلى تحديد الطرق التي تتفاعل بها الفئات المختلفة. يمكن تنفيذ هذا التفاعل بواسطة الكائنات لفئةٍ ما يستدعي أفعال كائنات من الفئات الأخرى. على سبيل المثال، يمكن أن يحتوي على فئة AppleOutputer التي تُخرج لون الصفيفة وطعمها من كائنات Apple، عن طريق استدعاء طريقة Display() لكل كائن من كائنات Apple.
فيما يلي الخطوات التي نقوم بها للقيام بتصميم OO:
- حدد الفئات وحدد بشكل عام كائن من فئات كل فئة يخزن كبيانات وما الذي يمكن أن يفعله الكائن.
- تحديد عناصر البيانات لكل فئة
- تحديد إجراءات كل فئة وكيف يمكن تنفيذ بعض الإجراءات لفئة واحدة
وتنفيذها باستخدام إجراءات الفئات الأخرى ذات الصلة.
بالنسبة إلى النظام الكبير، تحدث هذه الخطوات بشكل متكرر على مستويات مختلفة من التفاصيل.
بالنسبة لنظام قاعدة بيانات المؤلفين، نحتاج إلى فئة Composer التي تضم جميع البيانات التي نريد تخزينها على مؤلف فردي. يمكن لعنصر من هذه الفئة ترقية نفسه أو ترقيتها (تغيير ترتيبها)، وعرض سماتها.
نحتاج أيضًا إلى مجموعة من كائنات Composer. لهذا، نحدد فئة قاعدة البيانات الذي يدير السجلات الفردية. يمكن كائن من هذه الفئة إضافة أو استرداد كائنات المؤلفين، وعرض عناصر فردية من خلال استدعاء إجراء عرض كائن Composer.
وأخيرًا، نحتاج إلى واجهة مستخدم ما لتوفير عمليات تفاعلية على قاعدة البيانات. هذه فئة عنصر نائب، أي أننا لا نعرف حقًا ما التي تبدو عليها واجهة المستخدم حتى الآن، لكننا نعلم أننا سنحتاج إلى واحدة. ربما سيكون تصويريًا، ربما قائمًا على نص. في الوقت الحالي، نعرّف العنصر النائب الذي يمكننا ملؤها لاحقًا.
بعد أن حدّدنا فئات تطبيق قاعدة بيانات الموسّق، الخطوة التالية هي تحديد السمات والإجراءات للفئات. في التطبيقات الأكثر تعقيدًا، نجلس مع قلم رصاص وورقة أو UML أو بطاقات CRC أو OOD لتحديد التسلسل الهرمي للفئة وكيفية تفاعل العناصر.
بالنسبة لقاعدة بيانات المؤلفين، نحدد فئة Composer تحتوي على القيم البيانات التي نريد تخزينها على كل مؤلف. ويحتوي أيضًا على طرق لتعديل الترتيبات وعرض البيانات.
تحتاج فئة قاعدة البيانات إلى نوع من البنية للاحتفاظ بكائنات Composer. يجب أن نتمكّن من إضافة عنصر Composer جديد إلى البنية، بالإضافة إلى استرداد عنصر Composer محدّد. نود أيضًا عرض جميع الكائنات إما حسب الإدخال أو الترتيب.
تنفذ فئة واجهة المستخدم واجهة قائمة، مع معالِجات لإجراءات الاستدعاء في فئة قاعدة البيانات.
إذا كانت الفئات يسهل فهمها وكانت سماتها وإجراءاتها واضحة، كما هو الحال في تطبيق المؤلف، يسهل نسبيًا تصميم الفئات. ولكن إذا كانت لديك أي أسئلة حول كيفية ارتباط الفصول وتفاعلها، من الأفضل رسمها أولاً والعمل على التفاصيل قبل البدء في الترميز.
بمجرد أن نحصل على صورة واضحة للتصميم ونقيّمه (المزيد عن هذا قريبًا)، نعرّف الواجهة لكل فئة. لا نهتم في هذه المرحلة بتفاصيل التنفيذ ، بل نهتم فقط بالسمات والإجراءات وأجزاء حالة الصف وإجراءاته المتوفّرة للفصول الأخرى.
في لغة C++، نقوم بذلك عادةً من خلال تحديد ملف عنوان لكل فئة. الملحن تضم الفئة أعضاء بيانات خاصة لجميع البيانات التي نريد تخزينها في المؤلف. نحتاج إلى طرق وصول (طرق "get") وطرق تعديل (طرق "set")، بالإضافة إلى الإجراءات الأساسية للفئة.
// composer.h, Maggie Johnson // Description: The class for a Composer record. // The default ranking is 10 which is the lowest possible. // Notice we use const in C++ instead of #define. const int kDefaultRanking = 10; class Composer { public: // Constructor Composer(); // Here is the destructor which has the same name as the class // and is preceded by ~. It is called when an object is destroyed // either by deletion, or when the object is on the stack and // the method ends. ~Composer(); // Accessors and Mutators void set_first_name(string in_first_name); string first_name(); void set_last_name(string in_last_name); string last_name(); void set_composer_yob(int in_composer_yob); int composer_yob(); void set_composer_genre(string in_composer_genre); string composer_genre(); void set_ranking(int in_ranking); int ranking(); void set_fact(string in_fact); string fact(); // Methods // This method increases a composer's rank by increment. void Promote(int increment); // This method decreases a composer's rank by decrement. void Demote(int decrement); // This method displays all the attributes of a composer. void Display(); private: string first_name_; string last_name_; int composer_yob_; // year of birth string composer_genre_; // baroque, classical, romantic, etc. string fact_; int ranking_; };
تكون فئة قاعدة البيانات بسيطة أيضًا.
// database.h, Maggie Johnson // Description: Class for a database of Composer records. #include <iostream> #include "Composer.h" // Our database holds 100 composers, and no more. const int kMaxComposers = 100; class Database { public: Database(); ~Database(); // Add a new composer using operations in the Composer class. // For convenience, we return a reference (pointer) to the new record. Composer& AddComposer(string in_first_name, string in_last_name, string in_genre, int in_yob, string in_fact); // Search for a composer based on last name. Return a reference to the // found record. Composer& GetComposer(string in_last_name); // Display all composers in the database. void DisplayAll(); // Sort database records by rank and then display all. void DisplayByRank(); private: // Store the individual records in an array. Composer composers_[kMaxComposers]; // Track the next slot in the array to place a new record. int next_slot_; };
لاحظ كيف قمنا بتغليف البيانات الخاصة بالمؤلفين بعناية في قائمة الصف. كان بإمكاننا وضع هيكل أو فئة في فئة قاعدة البيانات لتمثيل سجل المؤلف، والوصول إليه مباشرةً من هناك. ولكن هذا الإجراء سيؤدي إلى استخدام "عدد قليل من الكائنات"، أي أنّنا لا نستخدم نماذج تحتوي على عدد كبير من الكائنات.
ستلاحظ عند بدء العمل على تنفيذ صفتي Composer وDatabase أنّه من الأفضل استخدام صفّة Composer منفصلة. على وجه الخصوص، يؤدي توفُّر عمليات ذرية منفصلة على عنصر Composer إلى تبسيط تنفيذ methods() Display() في فئة Database بشكل كبير.
بالطبع، هناك أيضًا ما يسمى "الإفراط في الاعتراض" أين نحاول جعل كل شيء فئة، أو لدينا فئات أكثر مما نحتاجه. يستغرق الأمر الممارسة لإيجاد التوازن الصحيح، وستجد أن المبرمجين الفرديين سيكون لها آراء مختلفة.
يمكن غالبًا تحديد ما إذا كنت تبالغ في استخدام أسلوب تجسيد الجنس أو لا تستخدمه بما يكفي من خلال وضع مخططات لفئاتك بعناية. كما ذكرنا سابقًا، من المهم تحديد فئة التصميم قبل البدء في البرمجة ويمكن أن يساعدك ذلك في تحليل نهجك. من الشائع العلامة المستخدمة لهذا الغرض لغة النمذجة الموحّدة (UML) الآن بعد أن أصبح لدينا الفئات المحددة لكائنات المؤلف وقاعدة البيانات، نحتاج إلى وهي واجهة تسمح للمستخدم بالتفاعل مع قاعدة البيانات. يمكنك استخدام قائمة بسيطة للقيام بذلك:
Composer Database --------------------------------------------- 1) Add a new composer 2) Retrieve a composer's data 3) Promote/demote a composer's rank 4) List all composers 5) List all composers by rank 0) Quit
يمكننا تنفيذ واجهة المستخدم كفئة أو كبرنامج إجرائي. لا كل شيء في برنامج C++ يجب أن يكون فئة. وفي الواقع، إذا كانت المعالجة متسلسلة أو موجّه للمهام، كما هو الحال في برنامج القائمة هذا، فلا بأس من تنفيذه بشكل إجرائي. من المهم تنفيذه بطريقة تجعله "عنصرًا نائبًا"، بمعنى، إذا أردنا إنشاء واجهة مستخدم تصويرية في مرحلة ما، فينبغي لنا لا تضطر إلى تغيير أي شيء في النظام غير واجهة المستخدم.
آخر شيء نحتاج إليه لإكمال الطلب هو برنامج لاختبار الفئات. بالنسبة لفئة Composer، نريد استخدام برنامج main() الذي يأخذ المدخلات ويملأ بشكل صحيح، ثم يعرضه للتأكد من عمل الفئة بشكل صحيح. نريد أيضًا استدعاء جميع طرق فئة Composer.
// test_composer.cpp, Maggie Johnson // // This program tests the Composer class. #include <iostream> #include "Composer.h" using namespace std; int main() { cout << endl << "Testing the Composer class." << endl << endl; Composer composer; composer.set_first_name("Ludwig van"); composer.set_last_name("Beethoven"); composer.set_composer_yob(1770); composer.set_composer_genre("Romantic"); composer.set_fact("Beethoven was completely deaf during the latter part of " "his life - he never heard a performance of his 9th symphony."); composer.Promote(2); composer.Demote(1); composer.Display(); }
نحتاج إلى برنامج اختبار مشابه لفئة قاعدة البيانات.
// test_database.cpp, Maggie Johnson // // Description: Test driver for a database of Composer records. #include <iostream> #include "Database.h" using namespace std; int main() { Database myDB; // Remember that AddComposer returns a reference to the new record. Composer& comp1 = myDB.AddComposer("Ludwig van", "Beethoven", "Romantic", 1770, "Beethoven was completely deaf during the latter part of his life - he never " "heard a performance of his 9th symphony."); comp1.Promote(7); Composer& comp2 = myDB.AddComposer("Johann Sebastian", "Bach", "Baroque", 1685, "Bach had 20 children, several of whom became famous musicians as well."); comp2.Promote(5); Composer& comp3 = myDB.AddComposer("Wolfgang Amadeus", "Mozart", "Classical", 1756, "Mozart feared for his life during his last year - there is some evidence " "that he was poisoned."); comp3.Promote(2); cout << endl << "all Composers: " << endl << endl; myDB.DisplayAll(); }
تجدر الإشارة إلى أن برامج الاختبار البسيطة هذه تعد خطوة أولى جيدة، ولكنها تتطلب منا لفحص الناتج يدويًا للتأكد من عمل البرنامج بشكل صحيح. مع زيادة حجم النظام، يصبح الفحص اليدوي للمخرجات غير عملي بسرعة. في درس لاحق، سوف نعرّفك على برامج الاختبار الذاتي في شكل لاختبارات الوحدة.
اكتمل تصميم تطبيقنا الآن. تتمثل الخطوة التالية في تنفيذ ملفات .cpp للفئات وواجهة المستخدم.للبدء، انتقل انسخ/ألصق h .واختبر رمز برنامج التشغيل أعلاه في الملفات، وقم بتجميعها.استخدام السائقين التجريبيين لاختبار صفوفك. بعد ذلك، نفِّذ الواجهة التالية:
Composer Database --------------------------------------------- 1) Add a new composer 2) Retrieve a composer's data 3) Promote/demote a composer's rank 4) List all composers 5) List all composers by rank 0) Quit
استخدم الطرق التي حددتها في فئة قاعدة البيانات لتنفيذ واجهة المستخدم. احرص على أن تكون طرقك خالية من الأخطاء. على سبيل المثال، يجب أن يكون الترتيب دائمًا في النطاق 1-10. لا تسمح لأي شخص بإضافة 101 مؤلف موسيقي أيضًا، إلا إذا كنت تخطط لتغيير البيانات في فئة قاعدة البيانات.
تذكر - يجب أن تتبع جميع التعليمات البرمجية اصطلاحات البرمجة لدينا، والتي يتم تكرارها هنا لتسهيل الأمر عليك:
- يبدأ كل برنامج نكتبه بتعليق في العنوان، يوفر اسم والمؤلف ومعلومات الاتصال به ووصف موجز واستخدامه (إذا كان ذلك منطبقًا). تبدأ كل دالة/طريقة بتعليق على العملية والاستخدام.
- نحن نضيف تعليقات توضيحية باستخدام جمل كاملة، كلما كانت التعليمات البرمجية وليس توثيقها بنفسه، على سبيل المثال، إذا كانت عملية المعالجة صعبة أو غير واضحة، أو شيقة أو مهمة.
- استخدم دائمًا أسماء وصفية: المتغيرات عبارة عن كلمات بأحرف صغيرة تفصل بينها _، كما في my_variable. تستخدِم أسماء الدوال أو الطرق أحرفًا كبيرة لتمييز الكلمات، كما في MyExcitingFunction(). تبدأ الثوابت بالحرف "k" و تستخدِم أحرفًا كبيرة لتمييز الكلمات، كما في kDaysInWeek.
- المسافة البادئة في مضاعفات الرقم اثنين. المستوى الأول هو مسافتَان، وإذا كان هناك حاجة إلى مزيد من المسافة البادئة، نستخدم أربع مسافات أو ست مسافات أو غير ذلك.
مرحبًا بك في العالم الحقيقي
في هذه الوحدة، سنقدم أداتين مهمتين للغاية تستخدمان في معظم هندسة البرمجيات المؤسسات. الأولى عبارة عن أداة إنشاء، والثانية هي إدارة التهيئة . كلتا الأداتين ضروريتان في هندسة البرمجيات الصناعية، حيث غالبًا ما يعمل العديد من المهندسين على نظام كبير واحد. تساعد هذه الأدوات في تنسيق التغييرات في قاعدة الرموز البرمجية والتحكّم فيها، كما توفّر وسيلة فعّالة لتجميع وربط نظام من العديد من ملفات البرامج وملفات الرأس.
Makefiles
تتم إدارة عملية إنشاء برنامج عادةً باستخدام أداة إنشاء، وهي تُجمِّع الملفات المطلوبة وتربطها بالترتيب الصحيح. في كثير من الأحيان، تحتوي ملفات C++ على على سبيل المثال، توجد دالة تم استدعاؤها في برنامج في برنامج آخر البرنامج. أو ربما تكون هناك حاجة إلى ملف رأس من خلال عدة ملفات .cpp المختلفة. حاسمة وأداة الإنشاء على تحديد ترتيب التجميع الصحيح من هذه التبعيات. وسيتم أيضًا compiling تجميع الملفات التي تم تغييرها فقط منذ آخر إصدار. ويمكن أن يوفر ذلك الكثير من الوقت في الأنظمة التي تتألف من عدة مئات أو آلاف الملفات.
يتم استخدام أداة إنشاء مفتوحة المصدر تُسمى make بشكل شائع. للاطّلاع على مزيد من المعلومات، يُرجى قراءة هذه المقالة. تحقّق ممّا إذا كان بإمكانك إنشاء رسم بياني للتبعية لتطبيق "قاعدة بيانات Composer"، ثم حوِّله إلى ملف makefile.إليك الحل الذي نقدمه.
أنظمة إدارة التهيئة
الأداة الثانية المستخدَمة في هندسة البرامج الصناعية هي إدارة الضبط (CM). ويُستخدَم هذا لإدارة التغيير. لنفترض أن خالد وسوزان كاتبان في مجال التكنولوجيا ويعمل كلاهما على تحديث الدليل الفني. خلال اجتماع، يحدّد مديرهم لكلّ منهم قسمًا من المستند نفسه لتعديله.
يتم تخزين الدليل الفني على جهاز كمبيوتر يمكن لكل من "بوب" و"سوزان" الوصول إليه. وفي حال عدم وجود أي أداة أو عملية حالية لمدير المنتدى، يمكن أن ينشأ عدد من المشاكل. أحد السيناريوهات المحتمَلة هو أنّه قد تم إعداد جهاز الكمبيوتر الذي يخزّن المستند بحيث لا يمكن لـ "بدر" و"سمر" العمل على الدليل في الوقت نفسه. سيؤدي هذا إلى إبطاء بشكل كبير.
ينشأ موقف أكثر خطورة عندما يسمح كمبيوتر التخزين بالوثيقة. ليتم فتحها بواسطة كل من بوب وسوزان في نفس الوقت. في ما يلي ما يمكن أن يحدث:
- يفتح أمجد المستند على جهاز الكمبيوتر ويعمل في القسم الخاص به.
- تفتح سوسن المستند على جهاز الكمبيوتر وتعمل على قسمها.
- يُكمل أمجد التغييرات ويحفظ المستند على كمبيوتر التخزين.
- تُكمل سوسن التغييرات وتحفظ المستند على الكمبيوتر المخصّص للتخزين.
توضح هذه الصورة التوضيحية المشكلة التي يمكن أن تحدث إذا لم تكن هناك عناصر تحكم. على نسخة واحدة من الدليل الفني عندما حفظت سوزان تغييراتها، يستبدل تلك التي أنشأها يوسف.
وهذا هو بالضبط نوع الحالات التي يمكن أن يتحكّم فيها نظام "مدير الحملة". باستخدام نظام إدارة المحتوى ، يمكن لكل من "بدر" و"سمر" "الاطّلاع على" نسختهما من الدليل الفني والعمل عليها. عندما يُعيد "بدر" تسجيل التغييرات، يعلم النظام أنّ "سوسن" قد استخرجت نسختها. عندما تتحقق سوزان من نسختها، قام النظام ويحلل التغييرات التي أجراها كل من بوب وسوزان وينشئ نسخة جديدة تدمج مجموعتي التغييرات معًا.
تتضمّن أنظمة إدارة المحتوى عددًا من الميزات التي تتجاوز إدارة التغييرات المتزامنة كما هو موضّح أعلاه. تقوم العديد من الأنظمة بتخزين أرشيفات جميع نسخ المستند، بدءًا من ووقت إنشائها. بالنسبة إلى الدليل الفني، قد يكون هذا مفيدًا جدًا عندما يكون لدى المستخدم نسخة قديمة من الدليل ويطرح أسئلة على الكاتب التقني. سيسمح نظام مدير المنتدى لكاتب التكنولوجيا بالوصول إلى الإصدار القديم لمعرفة ما يراه المستخدم.
تكون أنظمة إدارة الإصدارات مفيدة بشكل خاص في التحكّم في التغييرات التي يتم إجراؤها على البرامج. مثل تسمى أنظمة إدارة تهيئة البرامج (SCM). إذا كنت تفكر العدد الهائل من ملفات رمز المصدر الفردية في هندسة برامج كبيرة المؤسسة والعدد الهائل من المهندسين الذين يتعين عليهم إجراء تغييرات عليها، يكون من الواضح أن نظام إدارة الشؤون التجارية والعقود (SCM) بالغ الأهمية.
إدارة إعداد البرامج
تستند أنظمة SCM إلى فكرة بسيطة: النُسخ النهائية من ملفاتك في مستودع مركزي. يقوم الأشخاص بالتحقق من نسخ الملفات من المستودع، والعمل على هذه النسخ، ثم التحقق منها مرة أخرى عند الانتهاء منها. SCM تدير الأنظمة المراجعات وتتتبعها من قبل عدة أشخاص على صفحة رئيسية واحدة تعيين.
توفِّر جميع أنظمة "إدارة الشؤون التجارية والعقود" الميزات الأساسية التالية:
- إدارة التزامن
- تحديد الإصدارات
- المزامنة
لنلقِ نظرة على كل ميزة من هذه الميزات بمزيد من التفصيل.
إدارة التزامن
يشير مصطلح "المعالجة المتزامنة" إلى تعديل ملف في الوقت نفسه من قِبل أكثر من مستخدم. وفي حالة وجود مستودع كبير، نريد أن يكون الأشخاص قادرين على القيام بذلك، لكن يمكن أن يؤدي لبعض المشكلات.
بالنظر إلى مثال بسيط في المجال الهندسي: لنفترض أننا نسمح بالمهندسين لتعديل الملف نفسه في الوقت نفسه في مستودع مركزي لرمز المصدر. يحتاج كلّ من "العميل 1" و"العميل 2" إلى إجراء تغييرات على ملف في الوقت نفسه:
- يفتح العميل 1 bar.cpp.
- يفتح العميل2 bar.cpp.
- يغيّر Client1 الملف ويحفظه.
- يغيّر "العميل 2" الملف ويحفظه مع إلغاء تغييرات "العميل 1".
بالطبع، لا نريد حدوث ذلك. حتى لو تحكمنا في الموقف من خلال جعل المهندسين يعملان على نسخ منفصلة بدلاً من العمل بشكل مباشر على مدير (كما في الرسم التوضيحي أدناه)، يجب تسوية النسخ بطريقة ما. معظم الأقسام تتعامل أنظمة SCM مع هذه المشكلة من خلال السماح لعدة مهندسين بالتحقق من أحد الملفات ("مزامنة" أو "تحديث") وإجراء التغييرات حسب الحاجة. إدارة الشؤون التجارية والعقود ثم يقوم بتشغيل الخوارزميات لدمج التغييرات حيث يتم التحقق من الملفات مرة أخرى ("إرسال" أو "الالتزام") إلى المستودع.
يمكن أن تكون هذه الخوارزميات بسيطة (مطالبة المهندسين بحل التغييرات المتعارضة) أو ليست بسيطة للغاية (تحديد كيفية دمج التغييرات المتعارضة بذكاء ولا تسأل المهندس إلا إذا تعطل النظام بالفعل).
تحديد الإصدار
تشير تحديد الإصدارات إلى تتبع مراجعات الملفات مما يتيح إعادة إنشاء نسخة سابقة من الملف (أو العودة إليها). يتم ذلك إما عن طريق عمل نسخة مؤرشفة من كل ملف عند التحقق منه في المستودع، أو بحفظ كل تغيير يتم إجراؤه على أحد الملفات. يمكننا استخدام الأرشيفات في أي وقت أو تغيير المعلومات لإنشاء نسخة سابقة يمكن لأنظمة تحديد الإصدارات أيضًا إنشاء تقارير سجل حول من قام بإجراء التغييرات ومتى تم التحقق منها التغييرات.
المزامنة
في بعض أنظمة "إدارة الشؤون التجارية والعقود"، يتم تسجيل الملفات الفردية من المستودع وخارجه. تسمح لك الأنظمة الأكثر فعالية بالاطّلاع على أكثر من ملف واحد في المرة الواحدة. المهندسون مراجعة نسخة المستودع (أو جزء منها) الخاصة والكاملة والعمل في الملفات حسب الحاجة. ثم يُكملون تغييراتهم مرة أخرى إلى المستودع الرئيسي بشكل دوري، وتحديث نسخهم الشخصية للبقاء على اطلاع دائم بالتغييرات التي أجراها أشخاص آخرون. تُسمى هذه العملية المزامنة أو التحديث.
التخريب
Subversion (SVN) هو نظام مفتوح المصدر للتحكّم في الإصدار. وهي تحتوي على كل الميزات الموضحة أعلاه.
تتّبع أداة SVN منهجية بسيطة عند حدوث تعارضات. يحدث التعارض عندما يكون اثنان أو يقوم المزيد من المهندسين بإجراء تغييرات مختلفة على نفس المنطقة من قاعدة التعليمات البرمجية ثم يرسل كلاهما تغييراتهما. تنبه SVN المهندسين فقط بوجود نزاع - والأمر متروك للمهندسين لحله.
سنستخدم SVN خلال هذه الدورة لمساعدتك على التعرف على إدارة عملية الإعداد. هذه الأنظمة شائعة جدًا في الصناعة.
تتمثّل الخطوة الأولى في تثبيت SVN على نظامك. (يُرجى النقر.) هنا من أجل على التعليمات ابحث عن نظام التشغيل ونزِّل البرنامج الثنائي المناسب.
بعض مصطلحات SVN
- النسخة: تغيير في ملف أو مجموعة ملفات المراجعة واحدة "لقطة" في مشروع يتغير باستمرار.
- المستودع: النسخة الرئيسية التي يخزِّن فيها SVN سجلّ النُسخ السابقة الكامل للمشروع. يحتوي كل مشروع على مستودع واحد.
- النسخة العاملة: هي النسخة التي يُجري فيها المهندس تغييرات على مشروع. يمكن أن يكون هناك العديد من النُسخ الصالحة للاستخدام من مشروع معيّن يملك كلّ منها مهندس فردي.
- الإصدار: لطلب نسخة صالحة من المستودع. نسخة صالحة للعمل تساوي حالة المشروع عند التحقق منها.
- الالتزام: لإرسال التغييرات من نسخة عملك إلى المستودع المركزي. يُعرف أيضًا باسم تسجيل الوصول أو الإرسال.
- تحديث: لجلب ملاحظات الآخرين التغييرات من المستودع إلى نسخة عملك، أو للإشارة إلى ما إذا كانت نسخة العمل الخاصة بك تتضمن أي تغييرات غير مسموح بها. هذه العملية هي نفسها عملية المزامنة الموضّحة أعلاه. وبالتالي، يؤدي التحديث/المزامنة إلى تعديل نسختك المخصّصة للعمل لتصبح مطابقة لنسخة المستودع.
- التعارض: هو الموقف الذي يحاول فيه مهندسان إرسال تغييرات إلى الجزء نفسه من ملف. تشير SVN إلى حدوث تعارضات، ولكن على المهندسين حلها.
- رسالة السجلّ: تعليق ترفقه بمراجعة عند إجرائها، ويصف التغييرات التي أجريتها يقدّم السجلّ ملخّصًا لما يحدث في المشروع.
بعد تثبيت SVN، سنطّلع على بعض الأوامر الأساسية. تشير رسالة الأشكال البيانية أول شيء يجب فعله هو إعداد مستودع في دليل محدد. في ما يلي الأوامر:
$ svnadmin create /usr/local/svn/newrepos $ svn import mytree file:///usr/local/svn/newrepos/project -m "Initial import" Adding mytree/foo.c Adding mytree/bar.c Adding mytree/subdir Adding mytree/subdir/foobar.h Committed revision 1.
ينسخ الأمر import محتويات شجرة الدليل إلى مشروع الدليل في المستودع. يمكننا إلقاء نظرة على الدليل في مستودع يتضمّن الأمر list
$ svn list file:///usr/local/svn/newrepos/project bar.c foo.c subdir/
لا ينشئ الاستيراد نسخة صالحة. لإجراء ذلك، عليك استخدام الأمر svn checkout. يؤدي هذا إلى إنشاء نسخة عملية من شجرة الدليل. هيا نرى القيام بذلك الآن:
$ svn checkout file:///usr/local/svn/newrepos/project A foo.c A bar.c A subdir A subdir/foobar.h … Checked out revision 215.
الآن بعد أن أصبح لديك نسخة صالحة، يمكنك إجراء تغييرات على الملفات والدلائل هناك. إنّ النسخة المخصّصة للعمل هي مثل أي مجموعة أخرى من الملفات والأدلة، - يمكنك إضافة ملفات جديدة أو تعديلها ونقلها، ويمكنك حتى حذف النسخة المخصّصة للعمل بالكامل. لاحظ أنه إذا قمت بنسخ الملفات ونقلها في نسخة العمل الخاصة بك، من المهم استخدام نسخ svn ونقل svn بدلاً من أوامر نظام التشغيل. لإضافة ملف جديد، استخدِم إضافة svn ثم احذفه. استخدام ملف svn delete. إذا أردت تعديل كل العناصر، ما عليك سوى فتح ملف إلى المحرر الذي تستخدمه وتعديله!
هناك بعض أسماء الأدلة العادية التي يتم استخدامها غالبًا مع Subversion. "الصندوق" الدليل يحمل الخط الرئيسي للتطوير لمشروعك. "الفروع" الدليل يحمل أي إصدار فرع قد تعمل عليه.
$ svn list file:///usr/local/svn/repos /trunk /branches
لنفترض أنك أجريت جميع التغييرات المطلوبة على نسخة العمل وتريد مزامنتها مع المستودع. إذا كان هناك الكثير من المهندسين الآخرين يعملون في هذا الجزء من المستودع، من المهم إبقاء نسختك المخصّصة للعمل محدّثة. يمكنك استخدام أمر حالة svn لعرض التغييرات التي أجريتها. الذي تم إجراؤه.
A subdir/new.h # file is scheduled for addition D subdir/old.c # file is scheduled for deletion M bar.c # the content in bar.c has local modifications
لاحظ أن هناك الكثير من العلامات في أمر الحالة للتحكم في هذا الإخراج. إذا كنت تريد عرض التغييرات المحدّدة في ملف معدَّل، استخدِم svn diff.
$ svn diff bar.c Index: bar.c =================================================================== --- bar.c (revision 5) +++ bar.c (working copy) ## -1,18 +1,19 ## +#include +#include int main(void) { - int temp_var; + int new_var; ...
أخيرًا، لتحديث نسختك المخصّصة للعمل من المستودع، استخدِم الأمر svn update.
$ svn update U foo.c U bar.c G subdir/foobar.h C subdir/new.h Updated to revision 2.
هذا مكان قد يحدث فيه تعارض. في الإخراج أعلاه، تشير "U" تشير إلى لم يتم إجراء أي تغييرات على إصدارات المستودع من هذه الملفات وتعديل تم. "G" ما يعني حدوث دمج. كان لإصدار المستودع تم تغييرها ولكن التغييرات لم تتعارض مع تغييراتك. الحرف اللاتيني c يشير إلى خلاف. وهذا يعني أن التغييرات من المستودع تتداخل مع التغييرات التي أجريتها، والآن عليك الاختيار بينهما.
لكل ملف يتضمّن تعارضًا، يضع Subversion ثلاثة ملفات في نسختك العاملة:
- file.mine: هذا هو ملفك كما كان في نسختك المخصّصة للعمل قبل تعديل نسختك المخصّصة للعمل.
- file.rOLDREV: هذا هو الملف الذي تحققت منه من المستودع قبل وإجراء التغييرات.
- file.rNEWREV: هذا الملف هو الإصدار الحالي في المستودع.
يمكنك اتّخاذ أحد الإجراءات الثلاثة التالية لحلّ التعارض:
- انتقِل عبر الملفات وادمج الملفات يدويًا.
- انسخ أحد الملفات المؤقتة التي أنشأها SVN على نسختك الحالية.
- تنفيذ svn return لتجاهل جميع التغييرات التي أجريتها.
بعد حلّ التعارض، يمكنك إبلاغ SVN من خلال تشغيل svn resolved. يؤدي ذلك إلى إزالة الملفات المؤقتة الثلاثة، ولن يعرض SVN الملف في حالة تعارض.
آخر شيء يجب القيام به هو وضع نسختك النهائية في المستودع. هذا النمط تتم مع أمر svn Commit. عندما تلتزم بتغيير، تحتاج إلى لتوفير رسالة سجل تصف التغييرات التي أجريتها. يتم إرفاق رسالة السجلّ هذه بالمراجعة التي تنشئها.
svn commit -m "Update files to include new headers."
ويمكن التعرُّف على مزيد من المعلومات حول SVN وكيفية دعمه للبرامج الكبيرة. المشروعات الهندسية. تتوفّر موارد مفصّلة على الويب، ما عليك سوى إجراء بحث على Google عن "Subversion".
للتدريب، أنشئ مستودعًا لنظام قاعدة بيانات المؤلفين، وقم باستيراد جميع ملفاتك. ثم دفع نسخة صالحة والانتقال إلى الأوامر الموضحة أعلاه.
المراجع
الموقع الإلكتروني لبرنامج Subversion
التطبيق: دراسة في علم التشريح
تعرَّف على موقع eSkeletons من الجامعة. تكساس في أوستن