لوحة المتصدرين
المحتوى الأكثر حصولًا على سمعة جيدة
المحتوى الأعلى تقييمًا في 10/30/21 في كل الموقع
-
قمت بكتابة دالة للبحث في البرنامج : def search(self): con = pymysql.connect(host='localhost', user = 'root', password = '', database = 'employ') cur = con.cursor() cur.execute("select * from employees where " + str(self.se_by.get())+" LIKE '%"+ str(self.se_var.get())+"%'") rows = cur.fetchall() if len (rows) !=0: self.student_table.delete(*self.student_table.get_children()) for row in rows: self.student_table.insert("", END, value=row) con.commit() con.close() ولا تعمل! حيث تعطي الخطأ التالي : pymysql.err.ProgrammingError: (1064, "You have an error in your SQL syntax; check the manual that corresponds to your MySQL server version for the right syntax to use near 'LIKE '%%'' at line 1") الرجاء المساعدة2 نقاط
-
ما هو افضل backend يمكنني استخدامه لتطبيق flutter علما ان التطبيق عبارة عن تطبيق لإدارة المركبات يمكّن الشخص من معرفة معلومات سياراته كما يمكنه تجديد الرخصة او دفع رسوم المخالفات او التأمينات وغيرها..2 نقاط
-
2 نقاط
-
تقسم توابع النافذة window functions في SQL إلى 3 أقسام. توابع التجميع Aggregate functions مثل: COUNT AVG SUM MAX MIN توابع التصنيف أو الترتيب Ranking functions مثل: RANK ROW_NUMBER DENSE_RANK. توابع التحليل Analytic functions مثل: FIRST_VALUE LAST_VALUE LEAD LAG بشكل عام، تعمل توابع التجميع على إعادة قيمة واحدة كنتاتج معالجة قيم عمود ما، وعند استخدامها مع الدالة OVER مع عمل تقسيم الناتج لأجزاء أي نوافذ، يصبح عمل توابع التجميع بشكل مختلف حيث تعيد قيمة لكل مجموعة أسطر حسب التقسيم: إقراء عن دوال النوافذ في: دوال النافذة Window Functions:2 نقاط
-
بالنسبة للبرمجة فأغلب الحواسيب المتاحة حاليًا تفي بالغرض، وذلك لأن متطلبات البرمجة ليست كبيرة، ويمكن لحسوب من 10 سنوات أن يستخدم في أغلب أنواع البرمجة بدون مشاكل. أما بالنسبة إلى برامج التصميم والجرافيك، فالأمر مختلف قليلًا ومتفاوتة أيضًا، فعلى حسب البرامج المستعملة سيختلف الحد الأدنى من المتطلبات، ولكن يمكن أن نتفق على أن المواصفات التالية تفي بالغرض لأغلب برامج التصميم مثل Adobe Photoshop و Illustrator أو Cinema 4D .. إلخ: وحدة المعالجة المركزية CPU: يفضل أن تكون من نوع inter core i3 - i5 - i7 الجيل العاشر أو أعلى، أو AMD Rayzen 3600 وما فوق. ذاكرة الوصول العشوائي RAM: على الأقل 8 جيجابايت بسرعة 2600 MHz تخزين من نوع SSD: بمساحة 256 جيجابايت على الأقل. بعض أنواع المعالجات تحتوي على بطاقة رسوميات مثل معالجات AMD وبعض معالجات intel وحينها لن تحتاج إلى شراء بطاقة رسوميات عالية، وعلى الجانب الأخر توجد معالجات لا تحتوي على بطاقة رسوميات مثل معالجات intel من سلالة f (مثل intel i5 9400f (لاحظ يتنهي رقم المعالج بحرف f) )، أنصحك ببطاقة Nvidia GeForce GTX 1080 أو أعلى. بالمواصفات السابقة يمكنك العمل على البرامج التالية بسلاسة وبدون مشاكل: كل منتجات Adobe مثل Photoshop و After Effect منتجات AutoDesk مثل AutoCAD و Maya و 3Ds Max و Rivet المحررات البرمجية مثل VS Code و Android Studio برامج أنظمة التشغيل الإفتراضية مثل virtualbox و GenyMotion2 نقاط
-
مرحبا بالعالم يطبع البرنامج التالي العبارة مرحبًا بالعالم! في مجرى الخرج القياسي (standard output stream): #include <iostream> int main() { std::cout << "مرحبا بالعالم!" << std::endl; } يمكنك رؤية التجربة الحية على Coliru. تحليل البرنامج لنحلل كل جزء من شيفرة البرنامج بالتفصيل: #include <iostream> هو موجّه معالجة مسبقة (preprocessor directive)، ويتضمن محتوى iostream، وهي ترويسة ملف C++ القياسي (standard C++ header file). الكلمة iostream هي ترويسة ملف مكتبة قياسية (standard library header file)، وتحتوي على تعريفات مُجريَا الدخل والخرج القياسيين (standard input and output streams). هذه التعريفات مُتضمنة في فضاء الاسم std، كما هو موضح أدناه. يوفر مُجريا الدخل/الخرج القياسيين (I/O) طريقة للبرامج يمكنه عبرها جلب مدخلات من مخرجات خاصة بنظام خارجي، والذي يكون طرفية في العادة. int main() { ... } تُعرِّف هذه الشيفرة دالة جديدة باسم main تستدعى عند تنفيذ البرنامج. يجب أن تكون هناك دالة main واحدة لكل برنامج C++، ويجب أن تعيد دائمًا عددًا من النوع int. يمثل int نوع القيمة التي تعيدها الدالة والتي تمثل رمز الخروج (exit code) الخاص بالدالة main. البرامج ذات رمز الخروج المساوي للقيمة 0 أو EXIT_SUCCESS تُعد ناجحة من قبل النظام الذي ينفّذ ذلك البرنامج. ترتبط كل رموز الخروج الأخرى بأخطاء تحددها هي. في حال عدم استخدام التعليمة return، ستعيد الدالة main (وبالتالي البرنامج نفسه) القيمة 0 افتراضيًّا. في هذا المثال لا نحتاج إلى كتابة ;return 0. جميع الدوال الأخرى، باستثناء تلك التي تُعيد النوع void، يجب أن تُعيد قيمة بشكل صريح من النوع الذي يُفترض أن تعيده، أو لا ينبغي أن تعيد أي قيمة على الإطلاق. std::cout << "مرحبا بالعالم!" << std::endl; تطبع هذه التعليمة السلسلة النصية "مرحبا بالعالم!" في مجرى الخرج القياسي، وتتألف من الأجزاء التالية: std: هي فضاء اسم، و :: هو عامل تحليل النطاق (scope resolution operator) الذي يسمح بالبحث عن الكائنات بأسمائها ضمن فضاء الاسم. هناك العديد من فضاءات الاسم. هنا نستخدم :: لإظهار أننا نريد استخدام cout من فضاء الاسم std. لمزيد من المعلومات، ارجع إلى المقال Scope Resolution Operator في توثيق ميكروسوفت. std::cout: هو كائن مجرى الخرج القياسي (standard output)، وهو مُعرّف في iostream، ويُطبع أيّ شيء يُمرَّر إليه في مجرى الخرج القياسي (stdout). >>: هي، في هذا السياق، عامل الإدراج (stream insertion operator)، ويُسمى كذلك لأنه يدرج كائنًا في كائن المجرى (stream object). تُعرِّف المكتبة القياسية العامل >> لإدراج بعض أنواع البيانات في مجاري الخرج. تدرج التعليمة stream << content المحتوى content في المجرى stream ثم تعيد المجرى نفسه بعد تحديثه. يسمح ذلك بإجراء الإدراجات المتسلسلة، مثلًا، تطبع التعليمة std::cout << "Foo" << " Bar"; السلسة النصية "Foo Bar" في سطر الأوامر. "مرحبا بالعالم!": هي سلسلة نصية حرفية (character string literal). عامل الإدراج الخاص بالسلاسل النصية مُعرّف في الملف iostream. std::endl: هو كائن خاص لمعالجة مجرى I/O، وهو مُعرّف في الملف iostream. إنّ إدراج معالج في مجرى ما يغير حالة ذلك المجرى. يقوم معالج المجرى std::endl بعملين: أولًا، يدرج محرف نهاية السطر (end-of-line character)، ثم ينقل البيانات (flushes) الموجودة في المخزن المؤقت (stream buffer) الخاص بالمجرى لجعل النص يظهر في سطر الأوامر. يضمن هذا أنّ البيانات المُدرجة في المجرى ستظهر بالفعل في وحدة التحكم. (تُخزنّ بيانات المجرى عادة في مخزن مؤقت، ثم تُنقَل على دفعات، إلا إذا أمرت بنقلها فوريًا.) هناك طريقة بديلة تُجنِّب نقل البيانات من المخزن المؤقت، وهي: std::cout << "Hello World!\n"; // سطرًا جديدًا \n يمثل الفاصلة المنقوطة ;: تُخطر الفاصلة المنقوطة (;) المُصرّف (compiler) بأنّ التعليمة البرمجية قد انتهت. تتطلب كل عبارات C++ وتعريفات الأصناف استخدام فاصلة منقوطة في النهاية. التعليقات التعليقات هي جزء من الشيفرة يتجاهلها مصرف C++. تُستخدم التعليقات لتوضيح بعض الجوانب التي قد تكون غامضة بخصوص تصميم أو طريقة عمل البرنامج. هناك نوعان من التعليقات في C++: التعليقات السطرية (Single-Line Comments) تجعل الشرطتان المائِلتان // النص الذي يليها وحتى بداية السطر الجديد تعليقًا: int main() { // هذا تعليق سطري int a; // هذا تعليق سطري كذلك int i; // هذا أيضا تعليق سطري } التعليقات الكُتلية (Block Comments) يُستخدَم المحرفان /* للإعلان عن بداية تعليق كتلي، فيما يُستخدَم المحرفان */ للإعلان عن نهاية التعليق. يُفسَّر النص الموجود بين العبارتين على أنه تعليق، حتى لو كان النص الموجود بينهما شيفرة C++ صالحة. يسمى هذا النوع من التعليقات عادة تعليقات "C-style"، لأنّ صياغة هذ النوع من التعليقات موروثة من سلف C++، أي اللغة C: int main() { /* * هذا تعليق كتلي */ int a; } يمكنك كتابة ما تشاء في التعليقات الكتلية لكن عندما يجد المصرّف رمز نهاية التعليق*/، فإنه ينهي التعليق الكتلي: int main() { /* تعليق كتلي يمتد * على عدة أسطر * وينتهي في السطر التالي */ int a; } يمكن أيضًا أن تبدأ التعليقاتُ الكتلية وتنتهي في سطر واحد. مثلا: void SomeFunction(/* الوسيط الأول */ int a, /* الوسيط الثاني */ int b); أهمية التعليقات كما هو الحال مع جميع لغات البرمجة، توفر التعليقات العديد من الفوائد، منها: توثيق ضمني للشيفرة البرمجية لتسهيل قراءتها وصيانتها شرح الغرض من الشيفرة البرمجية ودوالها توفير تفاصيل حول تاريخ الشيفرة أو المنطق وراءها وضع حقوق الطبع والنشر/التراخيص، أو ملاحظات حول المشروع، أو شكر خاص، أو التنويه بالمساهمين، وما إلى ذلك مباشرة في الشيفرة المصدرية (source code). من جهة أخرى، فإنّ للتعليقات جانبًا سلبيًا كذلك: يجب تعديلها لتعكس أي تغييرات في الشيفرة الإفراط في إدراج التعليقات قد يؤثر سلبًا على مقروئية الشيفرة. يمكن تقليل الحاجة إلى التعليقات عبر كتابة شيفرة واضحة ومُوثقة ذاتيًا (self-documenting). أحد الأمثلة على ذلك هو استخدام أسماء توضيحية للمُتغيرات والدوال والأنواع. وتوزيع المهام المترابطة منطقيًا في دوال منفصلة. تعليق الشيفرة واختبارها أثناء التطوير، يمكن أيضًا استخدام التعليقات لتعطيل أجزاء من الشيفرة بسرعة دون حذفها. غالبًا ما يكون هذا مفيدًا في مرحلة الاختبار أو التنقيح (debugging)، ولكن ينبغي محو تلك التعليقات بعد الانتهاء. يشار إلى هذا غالبًا باسم "تعليق الشيفرة" (commenting out). بالمثل، الاحتفاظ بالإصدارات القديمة من أجزاء من الشيفرة في التعليقات لجعلها متاحة للمراجعة قد يكون مزعجًا، لأنّ ذلك يُراكم الكثير من الشيفرة غير المستخدمة، ولا يضيف قيمة تذكر موازنةً بالاطلاع على تاريخ الشيفرة عبر نظام إصدارات (versioning system). عملية التصريف القياسية في C++ تُنتَج برامج C++ القابلة للتنفيذ عادة بواسطة المُصرّف (compiler)، وهو برنامج يترجم الشيفرة من لغة برمجة إلى شيفرة تنفيذية تفهمها الآلة وتُنفذ على الحاسوب من قبل المستخدم النهائي. استخدام المصرّف لتصريف الشيفرة يسمى عملية التصريف (compilation process). ورثت C++ آلية التصريف من سلفها، أي اللغة C. فيما يلي قائمة توضح الخطوات الرئيسية الأربع للتصريف في C++: ينسخ المعالج الأولي (preprocessor) للغة C++ محتويات كل ملفات الترويسات (header files) المتضمنة في ملف الشيفرة المصدري، ويولد «شيفرة عملية بدل» (macro code، ويعوّض الثوابت الرمزية (symbolic constants) المعرفّة باستخدام #define بقِيمهما. تُصرَّف الشيفرة المصدرية الموسعة التي أُنتِجت بواسطة المعالج الأولي لـ C++ إلى لغة التجميع (assembly language) المناسبة لنظام التشغيل. تُصرَّف الشيفرة المجمّعة التي أُنتِجت بواسطة المصرّف إلى تعليمات مُصرّفة (object code) مناسبة لنظام التشغيل. تُربَط التعليمات المُصرَّفة المُولّدة من قبل المجمّع (assembler) مع ملفات التعليمات المُصرَّفة (object code files) الخاصة بدوال المكتبات المستخدمة لإنتاج الملف القابل للتنفيذ. ملاحظة: أحيانًا تُربط بعض الشيفرات المُصرَّفة معًا، ولكن ليس لغرض إنشاء برنامج نهائي، إذ يمكن تحزيم (packaging) الشيفرة "المربوطة" لأجل استخدامها من قبل برامج أخرى. الحزم الناتجة هي ما يشير إليه مبرمجو C++ بالمكتبات (libraries). تدمج العديد من مصرّفات C++ أو تفكّك بعض مراحل عملية التصريف لتسهيل العملية، أو لأجل التحليل الإضافي. يستخدم مُبرمجو C++ أدوات مختلفة، لكنها جميعًا تتبع المقاربة أعلاه. الدوال الدالة هي كتلة من الشيفرة تحتوي سلسلة من العبارات. يمكن للدوال قبول وسائط (arguments) أو قيم، ويمكن أن تعيد قيمة واحدة، أو قد لا تعيد أي قيمة. لاستخدام دالة، يجب استدعاؤها مع تمرير وسائط إليها، ثم تعيد قيمة. لكل دالة بصمة نوعيّة (type signature)، والتي تمثل أنواع وسائطها، ونوع القيمة المعادة. الدوال مستوحاة من مفهومَي الإجراء (procedure) والدالة في الرياضيات. ملاحظات: دوال C++ هي إجراءات (procedures) بالأساس، ولا تتبع بدقة قواعد الدوال الرياضية وفق مفهوم علوم الرياضيات. تؤدي الدوال في العادة مهام محددة ويمكن استدعاؤها من أجزاء أخرى من البرنامج. كما يجب التصريح عن الدالة وتعرِيفها قبل استدعائها في البرنامج. قد تُخفى تعريفات الدوال الشائعة والمهمة في ملفات أخرى مُضمّنة، فذلك يسهِّل إعادة استخدامها في البرامج. وهذا أحد الاستخدامات الشائعة لملفات الترويسات (header files). التصريح عن دالة التصريح عن دالة (Function Declaration) هو عملية الإعلان عن وجود دالة، مع توضيح اسمها وبصمتها النوعيّة للمصرف. ويتبع الصياغة التالية: // الدالة التالية تقبل عددًا صحيحًا وتعيد عددًا صحيحًا int add2(int i); في المثال أعلاه، تخبر int add2(int i) المصرّف بالمعلومات التالية عن الدالة المُصرَّح عنها: نوع القيمة المعادة هو int. اسم الدالة هو add2. عدد وسائط الدالة هو 1: الوسيط الأول من النوع int. سيُشار إلى الوسيط الأول داخل الدالة بالاسم i. اسم الوسيط اختياري؛ إذ يمكن التصريح بالدالة كما يلي: int add2(int); // يمكن حذف اسم الوسيط وفقًا لقاعدة التعريف الواحد (one-definition rule)، لا يمكن التصريح بدالة ذات بصمة (signature) نوعيّة معينة أو تعريفها أكثر من مرة واحدة في كامل شيفرة C++ المرئية للمصرّف. بمعنى آخر، لا يمكن إعادة تعريف دالة ذات بصمة نوعية معيّنة إلا مرة واحدة فقط. وبالتالي، فالشيفرة التالية غير صالحة في C++ وسيطلق المصرف خطأ عند تنفيذها: // int هي دالة من النوع add2 المصرف سيعلم أن int add2(int i); // (int) -> int // لا يمكن إعادة تعريف نفس الدالة بنفس البصمة int add2(int j); // (int) -> int إذا لم تُعِد الدالة أي قيمة، فإنّ نوع القيمة المعادة سيُكتب void. إذا لم تأخذ الدالة أيّ معاملات، فينبغي أن تكون قائمة المعاملات فارغة. // الدالة التالية لا تأخذ أي وسائط، ولا تعيد أي قيمة void do_something(); لاحظ أنه ما يزال بمقدور الدالة do_something التأثير في المتغيرات التي يمكنها الوصول إليها. استدعاء الدالة يمكن استدعاء الدالة بعد التصريح عنها. على سبيل المثال، يستدعي البرنامج التالي الدالة add2 مع القيمة 2 داخل الدالة main: #include <iostream> int add2(int i); // add2 تعريف الدالة int main() { // في هذا الموضع add2(2) سيتم تقييم std::cout << add2(2) << "\n"; // وستُطبع النتيجة return 0; } بالنظر إلى الشيفرة السابقة، نجد أنَّه ما تزال الدالة add2 تحتاج إلى توفير طريقة تنفيذ لها بالإضافة إلى تعريفها (جسم الدالة)، رغم أن تنفيذ الدالة لا يظهر مباشرة في الشيفرة، إذ يمكن جلبه من ملف آخر بربط هذا الملف مع الشيفرة التي تستدعي الدالة. تمثل add2(2) صياغة استدعاء دالة. تعريف الدالة تعريف الدالة يشبه التصريح عنها، إلا أنه يحتوي أيضًا على الشيفرة الذي ستُنفّذ عند استدعاء الدالة، هذه الشيفرة تُسمى جسم الدالة (function body). فيما يلي مثال عن تعريف الدالة add2: // i القيمة التي ستُمرر إلى الدالة سيشار إليها بالاسم int add2(int i) { // بين القوسين يمثل داخل نطاق أو جسم الدالة int j = i + 2; return j; } زيادة تحميل الدوال يمكنك إنشاء عدة دوال تشترك في نفس الاسم، ولكن مع اختلاف المعاملات، أي تختلف بصمة الدالة فقط، وهذا ما يعرف «بزيادة تحميل دالة» (Function Overloading). // التنفيذ الأول int add2(int i) { int j = i + 2; return j; } // التنفيذ الثاني int add2(int i, int j) { int k = i + j + 2; return k; // } كلتا الدالتين تحملان الاسم add2، بيْد أنّ الدالة المُنفّذة تعتمد على عدد وأنواع المعاملات المُمررة في الاستدعاء. في معظم الحالات، يمكن لمصرّف C++ أن يحدد الدالة المراد استدعاؤها. لكن في بعض الحالات يجب ذكر النوع بوضوح. فسيتم تقييم الشيفرة المحتواة في التعريف الأول عند استدعاء الدالة نع معامل واحد بالشكل add2(1). وفي حال استدعاء الدالة مع معاملين بالشكل add2(1, 3)، فسيتم تقييم الشيفرة المتضمنة في التعريف الثاني بدلًا من التعريف الأول. المعاملات الافتراضية يمكن تحديد القيم الافتراضية لمعاملات الدالة (Default Parameters) في تعريفات الدوال فقط. // 7 هي b القيمة الافتراضية للمعامل int multiply(int a, int b = 7); // جسم الدالة int multiply(int a, int b) { return a * b; } في هذا المثال، يمكن استدعاء multiply() مع معامل واحد أو مُعاملين. في حال تمرير معامل واحدة فقط، فستكون القيمة الافتراضية للمعامل b هي 7 وسيتم ضرب القيمة الممررة تلك بالمعامل الافتراضي 7 آنذاك. يجب وضع المعاملات الافتراضية في الأخير على النحو التالي: // تعريف صحيح int multiply(int a = 10, int b = 20); // مُقدّمة int a تعريف غير صحيح لأن int multiply(int a = 10, int b); استدعاءات خاصة للدوال - العوامل توجد استدعاءات خاصة في C++ للدوال، وهي ذات صياغة مختلفة عن الصياغة التقليدية name_of_function(value1, value2, value3). المثال الأكثر شيوعا هي العوامل (operators). وهي تسلسلات أحرف خاصة تحوَّل إلى استدعاءات دوال من طرف المصرّف، من أمثلة ذلك، ! و + و - و * و % و << وغيرها. عادةً ما ترتبط هذه المحارف الخاصة باستخدامات غير برمجية، أو تُستخدم للتبسيط (على سبيل المثال، المحرف + يمثل عادةً مفهوم الإضافة). تعالج C++ هذه التسلسلات؛ وتحوّل كل عامل إلى استدعاء الدالة المقابلة. على سبيل المثال، التعبير التالي: 3+3 يكافئ استدعاء الدالة التالية: operator+(3, 3) تبدأ جميع أسماء دوال العوامل بـ operator. في لغة C، لا يمكن تعيين معان جديدة لأسماء دوال العوامل عبر كتابة تعريفات إضافية ذات بصمات نوعية مختلفة، في C++، هذا جائز. يشار إلى ربط تعريفات إضافية بنفس اسم الدالة باسم التحميل الزائد للعامل (operator overloading) في C++، وهو اصطلاح شائع نسبيًا، ولكنه غير عام في C++. التصريح عن الدوال وقواعد مرئيتها في C++، يجب التصريح عن الشيفرة أو تعريفها قبل استخدامها. على سبيل المثال، ينتج عن الشيفرة التالية خطأ في وقت التصريف: int main() { foo(2); // (*) } void foo(int x) {} سيطلق خطأ، لأن الدالة foo استُدعِيت في السطر (*) قبل أن تُعرّف، إذ لا ترى الدالة main هذا التعريف المتأخر للدالة foo بعد استدعائها ضمنها. هناك طريقتان لحل هذه المشكلة: إما التصريح عن foo() أو تعريفها قبل استخدامها في main(). هذا مثال على ذلك: // ووضع جسمها أولا foo التصريح عن الدالة void foo(int x) {} int main() { // الآن foo يمكن استدعاء الدالة foo(2); } من الممكن أيضًا التصريح المسبق (forward-declaration) عن الدالة عن طريق وضع تصريح عن قالبها (prototype declaration) قبل موضع استدعائها والذي يحدد نوع القيمة المعادة واسم الدالة وعدد وسائطها وأنواعها، ثم تعريف جسمها لاحقًا: // foo تصريح عن قالب لدالة باسم void foo(int); int main() { foo(2); // (*) } // ينبغي أن تطابق تصريح القالب أعلاه void foo(int x) { // هنا foo تعريف جسم } أصبحت الدالة foo مرئية، لذا يمكن استدعاؤها الآن ضمن الدالة main في السطر (*) رغم أنها لم تُعرّف بعد. يجب أن يحدد تصريح النموذج نوع القيمة المعادة (void)، واسم الدالة (foo)، وأنواع المتغيرات في قائمة الوسائط (int)، لكنّ أسماء الوسائط غير ضرورية. يمكن وضع تصريحات قوالب الدوال في ترويسة الملف: // foo.h الملف // تصريح عن قالب دالة void foo(int); ثم تقديم التعريف الكامل في موضع آخر: // foo.cpp --> foo.o // foo تضمين الملف الذي يحوي قالب الدالة #include "foo.h" // foo تعريف جسم الدالة void foo(int x) { } ثم، بمجرد تصريف الشيفرة، يمكن ربط كائن الملف (object file) المقابل foo.o بالكائن المصرّف حيث يتم استخدامه في مرحلة الربط، main.o: // main.cpp --> main.o // foo تصريح عن قالب الدالة #include "foo.h" int main() { foo(2); } // (*) استدعاء الدالة foo ممكن هنا، لأنه سبق التصريح عنها، وتعريف قالب ومتن الدالة foo مربوط عبر كائنات الملفات (object files). يحدث الخطأ "رمز خارجي غير محلول" (unresolved external symbol) إذا صادف المصرّف تصريحَ قالب واستدعاء لدالة، دون وجود لجسمها (body). قد يكون حل هذه الأخطاء معقدًا، لأنّ المصرّف لن يبلِّغ عن الخطأ حتى مرحلة الربط النهائية، ولن يعرف السطر الذي يجب الانتقال إليه في الشيفرة لإظهار الخطأ. المعالج الأولي المعالج الأولي (Preprocessor) هو جزء مهم من المصرّف إذ يقوم بتعديل الشيفرة المصدرية، وحذف بعض البتات، أو تغييرها، وإضافة أشياء أخرى. في الملفات المصدرية، يمكننا تضمين مُوجِّهات (directives) للمعالج الأولي. تخبر تلك الموجِّهات المعالج الأولي بتنفيذ إجراءات محددة. يبدأ الموجّه بالرمز # في سطر جديد مثل: #define ZERO 0 من أشهر مُوجهات المعالج الأولي، الموجهات التالية: #include <something> يأخذ هذا الموجه something ويُدرجه في ملفك حيث يظهر الموجه. مثلا، يبدأ برنامج "مرحبا بالعالم!" بالسطر التالي: #include <iostream> يضيف هذا السطر الدوال والكائنات التي تتيح لك استخدام المدخلات والمخرجات القياسية. لا تحتوي اللغة C (والتي تستخدم أيضًا المُعالج الأولي) نفس القدر من ترويسات الملفات كخَلَفها C++، كما أنه يمكنك في C++ استخدام جميع ترويسات C. المُوجِّه الثاني في الأهمية هو على الأرجح الموجّه التالي: #define something something_else يخبر هذا المُوجِّه المعالج الأولي بأنه يجب أن يبدِّل something مكان كل ظهور لـ something_else في الملف الحالي. يمكن أن يجعل هذا الموجّه بعض الأشياء تتصرف مثل دوال، لكنه مفهوم متقدم أشرنا إليه قبل قليل على أنه «شيفرة عملية بدل» (macro code)، ولن نتطرق إليه الآن. something_else ليس ضروريًا، ولكن في حال عدم إضافة something، سيُحذَف كل ظهور لـ something. هذا مفيد جدا، إذ يمكن استخدامه مع المُوجِّهات #if و #else و #ifdef. وفق الصياغة التالية: #if something == true // شيفرة #else // شيفرة أخرى #endif #ifdef thing_that_you_want_to_know_if_is_defined // شيفرة #endif تدرِج هذه الموجِّهات الشيفرة الموجودة في البت الصحيح، وتحذف الآخرين. يمكن استخدام هذا لتضمين أجزاء من الشيفرة حصرًا في أنظمة تشغيل معينة دون الحاجة إلى إعادة كتابة الشيفرة بالكامل. هذه المقالة جزء من سلسلة مقالات عن C++. ترجمة -وبتصرّف- للفصل Chapter 1: Getting started with C++ من الكتاب C++ Notes for Professionals اقرأ أيضًا الدرس 2: القيم مصنَّفة النوع1 نقطة
-
السلام عليكم , منذ فترة أتتني رسالة من مايكروسوفت بأن ويندوز 11 أصبح جاهز للتثبيت , هل اثبته ؟ , مع العلم أنني مبتدئ في مجال الحاسوب و البرمجة و ادرس الآن دورة علوم الحاسوب و الشروحات الذي في الدورة المدرب يستخدم فيها ويندوز 10 لذلك لا اعلم ما الأفضل , هل أثبته أم استمر على ويندوز 10 ؟1 نقطة
-
الصورة التي قمت بإرفاقها هي بالفعل erd ﻻ تحتاج لتحويل ال Entity relational diagram (erd) هو عبارة عن نموذج يُعبر عن العﻻقات بين الكائنات وبعضها البعض فمثلاً في المثال التالي في الجامعة يقوم الطالب بالإشتراك في الدورات , ويجب على الطالب أن يكون مُشتركاً في دورة واحدة على الأقل أو أكثر, وكل دورة يتم تدريسها من قِبل مُعلم واحد فقط, وبغرض ضمان جودة الدورة يقوم المُعلم بتدريس دورة واحدة فقط نفهم من النص السابق : وجود عﻻقة كثير - كثير بين الطالب والدورات عﻻقة واحد - واحد بين الدورة والمُعلم فيمكن التعبير عنها بالشكل المرفق, ويتم إعتبار ذلك الشكل erd لأنه يُعبر عن العﻻقات بين الكائنات, مثل تماماً الشكل الذي قمت انت بإرفاقه1 نقطة
-
لدي الكود التالي، وأريد زيادة عرض خط الأسطر الموجودة ضمن ال legend: import numpy as np import matplotlib.pyplot as plt x = np.linspace(-2, 2, 50) y1 = x**3 y2 = x**2 fig, ax = plt.subplots() ax.plot(x, y1, linewidth=2.0, label='x^3') ax.plot(x, y2, linewidth=2.0, label='x^2') leg = ax.legend() plt.show()1 نقطة
-
أقوم بعملية تطبيع Normalizing للصورة باستخدام الدالة cv2.normalize لتغيير نطاق قيم كثافة البكسل (أريد جعلها بين ال 0 وال 1)، وقمت بتطبيق خوارزمية NORM_L1 لكن لم ينجح الأمر؟ وتظهر الصورة سوداء بقيم صفرية. import cv2 import Image import numpy myimage = cv2.imread('/content/test5 openeye.jpg',0) img_normlaized=cv2.normalize(myimage,None,alpha=0,beta=1,norm_type=cv2.NORM_L1) image = Image.fromarray(img_normlaized) image.save("image.jpg")1 نقطة
-
كيف يمكننا التلاعب بلون الخلفية والشفافية للرسم البياني؟1 نقطة
-
بافتراض لدي جدول رواتب موظفين وأريد ترتريبهم تصاعدياً مع إضافة ترقيم يدل على ترتريب راتب الموظف كيف أستطيع عمل ذلك؟ وهل يمكن تحديد التريب حسب كل قسم؟1 نقطة
-
يمكن إعطاء رتبة لكل سطر من الاستعلام باستخدام دالة ()RANK في SQL فهي تعيد ترقيم تصاعدي بدءاً من 1 ولأعلى لكل سطر حسب شرط الترتيب. وتعتبر هذه الدالة من دوال النافذة window function وتعمل مع دالة OVER لتحديد المجالات الجزئية للترقيم وعلاقة الترتيب. لعمل ترقيم شامل لكل الموظفين حسب ترتيب الراتب، لن نقوم بعمل تجزئة للبيانات، أي بدون PARTITION سوف نمرر لدالة OVER علاقة الترتيب حسب الراتب فقط: SELECT *, RANK() OVER (ORDER BY salary) salary_rank FROM employees; والنتيجة: أما لعمل ترتيب جزئي حسب كل قسم، علينا تجزئة البيانات باستخدام PARTITION مع تمرير رقم القسم ثم علاقة الترتيب: لاحظ التجزئة حسب رقم الفرع: SELECT *, RANK() OVER ( PARTITION BY department_id ORDER BY salary ) salary_rank FROM employees والنتيجة:1 نقطة
-
لنقم بإستكمال عملك و تحقيق ذات الغرض , ثم سنأتي لطريقة الأجاكس بعد ذلك : في ملف العرض display_items.php : نقوم بإستعمال حقل من نوع radio بدلا عن checkbox كونه عملي أكثر , و لنتأكد من إعطاء كامل الحقول ذات الإسم و ذلك كالتالي : <?php foreach ($category_data->result_array() as $row) { ?> <label> <input name="filter_cats" type="radio" value="<?php echo $row['category_name'];?>"> <?php echo $row['category_name']?> </label> <?php } ?> لنتأكد من إعطاء كامل أسطر الجدول صنفا عاما كالتالي : item و صنفا اخر خاصا كالتالي : item_[category_name] ليصبح : foreach($result as $row): echo "<tr class='item item_".$row->category_name."'>"; ثم لنقم بإضافة التفاعلية التالية : $("input[type='radio']").change(function() { if(this.checked) { let target = $(this).val(); $('.item').hide(); $('.item_' + target).show(); } }); يلخص هذا الطريقة الأولى . هاته الطريقة عملية و بسيطة في حالة محدودية المنتجات لديك , و لكن إن كنت مضطرا لإستعمال AJAX فسنلجأ لإستعمال الطريقة الثانية : و لكن هاته الطريقة أيضا غير عملية كونك لا تقوم بفصل جدول العناصر عن جدول الفئات , بل تضيف عمودا خاصا لإسم الفئة داخل جدول العناصر , و في هاته الحالة لن يمكنك الإستعلام من جدول منفصل بل من ذات الجدول الذي تريد جلب العناصر منه . و يجعل هذا العملية غير منطقية و غير عملية . فلا يوجد داع من : الإستعلام مرة أولى من الجدول tbl_items لقراءة أسماء الفئات . الإستعلام مرة ثانية من الجدول tbl_items لقراءة المنتجات الحاملة لإسم فئة معين . و بالتالي فإن أفضل حل هو ما تم إعتماده في الطريقة الأولى . خصوصا و أن المسار , ملف العرض و المتحكم يخصون المنتجات .1 نقطة
-
السلام عليكم لم اعرف كيف اطبق ال jquery ajax ولكن عرضت الجداول في ال view .98401 نقطة
-
هل تأكدت من حفظ الملف بعد التعديل؟ اعتقد أن الخطا بسبب تداخل علامات الاقتباس حاول تجزئة العبارة بطريقة صحيحة1 نقطة
-
حاول تعديل السطر كالتالي: str(self.se_by.get())+" LIKE '%'"+ str(self.se_var.get())+"'%'") أو: str(self.se_by.get())+" LIKE " + '%' + str(self.se_var.get())+ '%')1 نقطة
-
مرحبا, احاول التعامل مع api احدى الشركات عن طريق لارافيل, api يتطلب استخدام vpn, هل يجب عليا تشغيل vpn فقط على جهازي ام يوجد شيئ أخر يجب القيام به في لارافيل؟1 نقطة
-
بافتراض أن خدمة API service التي تعتمد عليها في مشروعك ويتصل تطبيق لارافل عليها محظورة في بلدك (بلد المطور) ولا تستطيع طلب الخدمات بدون VPN أثناء التطوير بسبب IP جهازك حالياً، فهذا الأمر بسبب البلد، أي بعد رفع المشروع على استضافة لدولة غير محظورة من هذه الشركة سيتصل تطبيق لارافل من استضافتك على الخدمة بدون مشكلة. تطبيق لارافل يتعمد على نظام التشغيل في إدارة الاتصال مع الشبكة، فهذا الموضوع غير متعلق بالمشروع نفسه. الكلام السابق صحيح حسب وصفك للمشكلة، حيث أن المستخدم العادي لايطلب الخدمة بشكل مباشر من بلد محظور.1 نقطة
-
الخطأ 403 يعني أنك ﻻ تمتلك صﻻحية الوصول إلى الresources المطلوبة ع الرغم من إثبات هويتك, فلو كنت لم تثبت هويتك بعد لكان الخطأ المُفترض أن تحصل عليه هو 401 authorization error حسب المعايير القياسية لتصميم الapis فإن ذلك الخطأ بشكلٍ غالب يعني أنك قمت بطلب بيانات ﻻ يمكنك رؤيتها, كمثال إن كنت تطلب بيانات ينقصر رؤيتها على المدير(admin) أو مثلاً تطلب بيانات لمستخدم أخر وليس لك الحق في طلبها. كما من الممكن أن يكون الحساب الخاص بك ليس مُفعل بعد1 نقطة
-
1 نقطة
-
هناك عدة حلول منها: يمكنك حذف مجلد build عن طريق الأمر التالي flutter clean ثم تقوم بجلب الحزم مرة أخرى عن طريق الأمر التالي flutter pub get ثم تقوم بتشغيل التطبيق عن طريق الأمر التالي flutter run و هناك حل آخر عن طريق حذف مجلد .gradle في مسار جهازك , قد يكون المسار كالتالي C:\Users\هنا اسم جهازك ثم تقوم بتشغيل التطبيق مرة أخرى. أيضا يمكنك فتح المسار التالي project\android\build.gradle و تغيير إصدار gradle كما في السطر التالي dependencies { classpath 'com.android.tools.build:gradle:7.0.3' } ثم في ملف gradle-wrapper.properties تغير قيمة distributionUrl كالتالي distributionUrl=https\://services.gradle.org/distributions/gradle-7.0.2-all.zip1 نقطة
-
في الغالب و بشكل عام , يعني هذا الخطأ أنه قد تم حظر عنوان IP الذي تتصل منه بواسطة إحدى خدمات الأمان للخادم الذي ترسل إليه طلبات الـ Http . و ليس المقصود بحظر عنوان IP الخاص بك حظره هو ذاته بالضرورة , بل قد يكون الحظر شمل عناوين ذات البلد مثلا . مثال : إذا كنت تتصل من شبكة مشتركة ، تكون فيها البنية التحتية للإنترنت أقل تطورًا ، فإن عناوين IP نادرة (من المرجح أن يشاركها الكثير من الأشخاص) ، فهذا يمكن أن يزيد من احتمالية رؤية هذا الخطأ . في هاته الحالة ، كإقتراح على ما يمكنك فعله هو محاولة الاتصال من شبكة أخرى مؤقتًا . أما و بشكل خاص , فستحتاج البحث في توثيق الواجهة البرمجية التي تتصل بها على دلالية كود الخطأ 1003 و إصلاح المشكلة بعد تحديدها , فقد يحتمل مثلا أن بيانات المصادقة التي تستعملها قد استهلكت .1 نقطة
-
هل الكورس له فترة معينة وينتهي ولا يظهر على صفحتي لاني مشغول في هذه الفترة واريد ان اتعلم بالاجازة ام الكورس يظل متوفر معي1 نقطة
-
أردت الاستفسار في حالة أني انسحبت من البرنامج وأردت إلغاء إشتراكي في برنامج حسوب هل سيتم إسترجاع المبلغ لي ؟! أم أصبح هذا الخيار ليس متاحا لي ؟؛1 نقطة
-
تستخدم الدالة OVER لانتقاء الأسطر من ناتج الاستعلام التي نريد تطبيق عليهم دالة معينة (دالة تجميع aggregation function)، وتفيد في عمل ترتيب أو حسابات عليهم، من خلال تقسيمهم إلى أجزاء partitions ولذلك تستخدم مع window functions. الشكل العام لها: <function> OVER ( [PARTITION BY clause] [ORDER BY clause] [ROWS or RANGE clause]) حيث نمرر لها غلاقة clause نحدد فيها الأسطر المستهدفة. في حال لم يتم تمرير غلاقة يكون استعلام على كامل الجدول في حال تم تمرير عبارة التقسيم يتم تطبيق العملية على الأسطر / السجلات المختارة مثلا يمكن جلب إجمالي أرباح شركة ثم حساب المجموع الجزئي حسب الفرع بعمل تقسيم حسب الدولة SELECT year, country, product, -- المنتج profit, -- ربح النتج SUM(profit) OVER() AS total_profit, -- ربح إجمالي يجمع الأرباح SUM(profit) OVER(PARTITION BY country) AS country_profit -- تقسيم الربح حسب الدوالة FROM sales ORDER BY country, year, product, profit; والنتيجة: أصبح باستطاعتنا تحديد ربح منتج الحاسوب بالنسبة لأرباح فرع فنلندا الإجمالية مثلا.. 1500/1610 في سنة 2000 مثال آخر، حساب قيم أعمدة جدول من خلال عمل حسابات جزئية على ناتج الاستعلام، في مدرسة نريد عمل تحليل حسب متوسط العمر و عدد الطلاب والنتائج يتم فرزهم حسب الجنس. SELECT id, name, gender, -- عدد الطلاب حسب الجنس COUNT(gender) OVER (PARTITION BY gender) AS Total_students, -- متسوط العمر حسب الجنس AVG(age) OVER (PARTITION BY gender) AS Average_Age, -- مجموع الدخل حسب الجنس SUM(total_score) OVER (PARTITION BY gender) AS Total_Score FROM student والنتائج:1 نقطة
-
يمكنك الإطلاع على هذه الإجابات هنا، حيث تحتوي على عدد جيد من المصادر التي يمكنك البدء منها: يمكنك أيضًا أن تقرأ مقالات متقدمة في مجال الحماية من خلال قسم Devops/Security من هنا1 نقطة
-
الآمن السبراني هو عملية لحماية الأنظمة والأجهزة في مؤسسة ما بهدف منع الهجمات الإلكترونية عليها، حيث تحتوي هذه الأنظمة والأجهزة -المراد حمايتها- على معلومات سرية أو حساسة. وتهدف الهجمات الإلكترونية هذه إلى الوصول إلى هذه المعلومات بغية تسريبها أو تغيرها أو حتى تدميرها. وينقسم المن السيبراني إلى طبقات من الحماية تنتشر عبر أجهزة الكمبيوتر والشبكات والبرامج (وفي بعض الأحيان الأشخاص) التي يجب حمايتها والحفاظ عليها، ويتم الإتفاق وتقسيم هذه الطبقات حسب رغبة المؤسسة أو المنظمة وما تراه مناسبًا لها. بعض أنواع التهديد في الأمن السيبراني تصيد المعلومات Phishing Information ويتم في هذه العملية إرسال محتوى زائف مشابهة لمحتوى أصلي موثوق، مثل إرسال رسالة إلى بريد إلكتروني مزورة أو إرسال رابط موقع مشابهة لموقع معروف مثل Facebook على سبيل المثال، وهذا بغرض الحصول على معلومات حساسة وسرية، مثل كلمات المرور أو أرقام البطاقات الإتمانية أو أجوبة لأسئلة سرية .. إلخ، وبالطبع قد ينخدع عدد من المستخدمين في مثل هذا الإحتيال ظنًا منهم أنه هذا هو الموقع الحقيقي أو أن هذه عبارة عن رسالة رسمية موثوقة. البرامج الضارة Malware وهي برامج يتم إرسالة إلى الضحايا بأي شكل كان، وتهدف هذه البرامج إلى الوصول الكامل لجهاز الكمبيوتر والتحكم فيه عن بعد بطريقة غير مرئية، بهدف إستخراج معلومات سرية أو مراقبة صاحب الجهاز أو حتى الوصول إلى أجهزة أخرى في المؤسسة .. إلخ، وهذه البرامج الضارة يتم الكشف عنها في الغالب عن طريق طرق بسيطة مثل إستخدام برامج الحماية Anti-Virus أو من خلال طرق أكثر تعقيدًا مثل مراقبة نشاطات البرنامج وما يحاول القيام به في بيئة إفتراضية معزولة Isolated Virtual Environment .. إلخ. برامج الفدية الضارة Ransomware وهي برامج ضارة تحاول تشفير كل محتويات جهاز الكمبيوتر (أو نوع معين من الملفات)، بغرض من صاحب الجهاز من الوصول إلى هذه الملفات أو إستخدامها إلا بعد دفع مبلغ مالي (فدية) إلى صانع برنامج الفدية، وفي الغالب تتم هذه العملية عبر عملة مشفر يصعب تتبع صاحبها مثل البيتكوين أو إيثريوم Ethereum وغيرها. وفي كثير من الأحيان تفشل المنظمة (أو صاحب جهاز الكمبيوتر) من إعادة الملفات المشفرة وفك تشفيرها. متطلبات الأمن السيبراني يحتاج متخصص الأمن السيبراني إلى تعلم الكثير من المهارات التي تُمكنه من التعرف على التهديدات وكيفية مواجهتها، كما أنه بحاجة إلى مهارات التفكير بشكل تحليلي ونقدي وحل المشكلات والقدرة على العمل بشكل جيد سواء كفرد أو ضمن فريق. أيضًا المهارات التقنية مثل تعلم لغات البرمجة والخوارزميات وبنية البرامج والأنظمة وكيفية حمايتها من التهديدات تُعد مهارات أساسية لا غنى عنها. كما أنه بحاجة إلى القراءة عن أشهر الثغرات وكيفية عملها وإستغلالها وكيفية الحماية منها أيضًا.1 نقطة
-
إن أكاديمية حسوب لاتوفر دورات خاصة ببرمجة الألعاب, حتى تستطيع برمجة الألعاب يجب أن يتوفر لديك مهارات التفكير المنطقي و الخوارزميات وأساسيات البرمجة، فإن كنت مبتدئ أنصح بالتعلم بشكل صحيح من البداية ليكون لديك معرفة واسعة. وبشكل متقدم، برمجة الألعاب تحتاج لمهارات في الرياضيات.. إن قمت بتسجيل دورة علوم الحاسب المقدمة من أكاديمية حسوب سوف تستطيع تعلم لغة برمجة سكراتس وإنشاء ألعاب تفاعلية مع تحريك الأجسام وتغيير الخلفيات والقفز و إطلاق الرصاص من الدبابة وكلها أساسيات عالم الألعاب وغيرها من الأمور الأساسية لكي تصبح قادراً على تعلم لغة برمجة حقيقية ثم بعدها برمجة الألعاب. أنصح بهذه الدورة. مدخل إلى علوم الحاسوب أساسيات البرمجة أنظمة التشغيل ونظام لينكس قواعد البيانات إلى عالم الويب البرمجة كائنية التوجه الخوارزميات وبنى المعطيات أنماط التصميم هذه أمثلة تدرس في الدورة1 نقطة
-
علوم الحاسوب computer science (أو يطلق عليه علوم الكمبيوتر أو علوم الحاسبات بالعامية) هو روح العصر الحديث ويمثل مجال التقنية التي دخلت في كل مفاصل حياتنا، فهو يدخل في كل صغيرة وكبيرة من حولنا بدءًا من الهواتف المحمولة والحواسيب والآلات والمصانع وكل شيء بما لا يمكن حصره من المنتجات والخدمات حيث أسهم في نقلنا إلى مرحلة أعلى من الفعالية والجودة والفائدة. هذا المقال هو دليل شامل للتعرف على اختصاص علوم الحاسب وفوائده وتطبيقاته والمواد التي تدرس فيه والمجالات المتفرعة منه ثم سنناقش بعض الأسئلة المهمة المتعلقة بجدوى اختيار تخصص علوم الحاسوب ودراسته وما هي فرص العمل المتوفرة في مجال علوم الحاسوب وسنعرف أيضًا الفرق بين علم الحاسوب وهندسة الحاسوب وأخيرًا سنرشدك إلى كيفية البدء في دراسة اختصاص علوم الحاسب ذاتيًا وندلك على بداية الطريق لتنطلق إن أحببت المجال. هل أنت جاهز؟ لننطلق! فهرس المحتويات حرصًا على تنظيم المقالة ولتسهيل الوصول إلى القسم الذي تريده بسهولة، سنذكر هنا جدول المحتويات باختصار: ما هي علوم الحاسب؟ فوائد وتطبيقات علوم الحاسوب هل تعلم علوم الحاسوب مفيد؟ مواد علوم الحاسوب تخصصات علوم الحاسب كيف أختار التخصص المناسب لي من تخصصات علوم الحاسب؟ التوظيف وفرص العمل في مجال علوم الحاسوب ما الفرق بين علوم الحاسب وهندسة الحاسب؟ من أين أبدأ بتعلم علوم الحاسب؟ ما هي علوم الحاسب؟ تُعرَف علوم الحاسوب بأنها كل العلوم المتعلقة بالآلات الحسابية والمنطقية، حيث تعمل بدارات إلكترونية، وقد تكون علومًا نظريةً مثل نظرية المعلومات Information Theory أو تطبيقية مثل البرمجيات Software أو العتاد Hardware. والبرمجيات software هي الأنظمة غير الملموسة التي تُشغّل عتاد الحواسيب hardware وتقوم بمهام المستخدمين المطلوبة، بينما العتاد هو القطع الفيزيائية الملموسة التي تُكوّن النظام الحاسوبي مثل المعالِج والذاكرة العشوائية RAM والقرص الصلب …إلخ. وتتشعّب علوم الحاسوب وتترابط مع بعضها بعضًا، إذ لا يمكنك دراسة البرمجة دون دراسة الخوارزميات وهياكل البيانات، كما لا يمكنك دراسة علوم الذكاء الاصطناعي دون دراسة الجبر الخطّي والاحتمالات وعدد آخر من علوم الرياضيات. فوائد وتطبيقات علوم الحاسوب نَذكُر من فوائد وتطبيقات علم الحاسوب ما يلي: تعد شبكة الإنترنت واحدة من أفضل الاختراعات في التاريخ الإنساني، فهي شبكة حاسوبية تصل مناطق العالم ببعضها البعض. حواسيب سطح المكتب والحواسيب المحمولة التي نعمل عليها أو نرفّه خلالها عن أنفسنا …إلخ، جميعها قائمة على علوم الحاسوب وتخصصات الحاسب. الهواتف المحمولة وتطبيقات الهواتف الذكية التي نستعملها للتسوق أو طلب سيارة أجرةٍ أو طلب الطعام والدواء وغير ذلك فكلها تطبيقات حاسوبية. أجهزة إدارة المصانع والعمليات التصنيعية التي تُستعمل لتخطيط تصنيع المنتجات الغذائية وغير الغذائية، فهي مبنيّة بالأساس على برامج وخوارزميات ورياضيات حاسوبية، فلولاها لما تطورت أنظمة التصنيع التي لدينا إلى مرحلة تكفي احتياجات الجنس البشري. الأجهزة الطبية التي تُنقَذ بها حياة الناس في المستشفيات، وأجهزة الاستشعار والإدارة العسكرية، والأجهزة التي تساعد على إدارة الأسواق الاقتصاد والمال وغيرها، فكلها موجودة بفضل علوم الحاسوب. ولعلك تشاهد بعضًا من فوائد هذه العلوم بنفسك، فلا تحتاج من يخبرك عنها. دورة علوم الحاسوب دورة تدريبية متكاملة تضعك على بوابة الاحتراف في تعلم أساسيات البرمجة وعلوم الحاسوب اشترك الآن هل تعلم علوم الحاسب مفيد؟ قد تفكر في نفسك الآن وتقول هل من المجدي لي أن أتخصص في مجال علم الحاسوب وأتعلمها سواء للدراسة والبحث الأكاديمي أو للعمل والوظيفة مستقبلًا؟ والجواب هو نعم، فالأمر مُجدٍ وأنصح به بشدة، إذ يحصل متخصصو علوم الحاسوب على رواتب أعلى من العديد من نظرائهم المهندسين غير العاملين في المجال، كما تزداد الرواتب بازدياد الخبرة ونُدرة المجال الذي يتخصص فيه المرء؛ فمثلًا لا يتساوى مُطوّر واجهات الاستخدام بمهندس يجيد الذكاء الاصطناعي وتعلم الآلة، وكذلك لا يتساوى مع مهندسي البرمجيات ومهندسي العتاد. وبغض النظر عن عامل الوظيفة والراتب والمال، يساعدك تعلم علوم الحاسوب على بناء مشاريع حقيقيةٍ يمكنك أن تستفيد منها أو تغيّر بها العالم، مثلًا انظر كيف غير مارك زوكيبربيرغ طريقة تواصل المليارات من البشر وكامل التاريخ الإنساني بسبب تطويره لموقع إنترنتٍ، وانظر كذلك كيف تتحكم العملات الرقمية وتغير الاقتصاد العالمي في وقتنا الحالي. ويؤهلك تعلم علوم الحاسب لإنشاء خدمات ومواقع وبرمجيات وأنظمة حسبما تحتاج، ومن ثَم تستفيد منها سواءٌ لنفسك أو تبيعها للآخرين إما على شكل وظيفةٍ، أو خبرةٍ كما في مجال العمل الحُرّ. ولا يعني هذا بالطبع أنك ستكون مالك شركة مايكروسوفت المستقبلي، وكذلك لن تكون مارك الذي سيغيّر العالم، إلا أنك ستمتلك نفس الخبرات والأدوات التي امتلكوها عندما فعلوا ذلك، أما الباقي فيعتمد عليك وعلى اجتهادك ومثابرتك. مواد علوم الحاسب سنتحدث في هذا القِسم عن مواد تخصص علوم الحاسب وتفريعاتها المختلفة، وأبرز المواد التي تُدرّسها معظم الجامعات حول العالم لطلابها الراغبين بتَعلّم هذا التخصص، ومعظم هذه المواد مُستوحاةٌ من الفهرس الأكاديمي لجامعة ستانفورد لعلوم الحاسبات، هذا بالإضافة لعددٍ من الجامعات الأخرى. 1. أساسيات الهندسة وهندسة البرمجيات تعد من أول المواد التي يدرسها الطلاب وتهدف للإجابة على أسئلة متنوعة مثل ما هو تعريف الهندسة وما هي عمليات الإدخال والإخراج وكيف تصمم منتجًا أو برنامجًا أو مشروعًا وما هي الخطوات العملية لتصميم وهندسة نظام ما وغيرها. والهدف الرئيسي من هذه المادة هو نقل وتوصيل معنى الهندسة للطلاب، وذلك بإدراكهم أن عملية إنشاء الأنظمة هي خليطٌ مُتقنٌ من التخطيط والترتيب، فهي ليست عمليةً عشوائيةً بسيطةً، وعادةً لا تحتوي هذه المادة على مشاريع برمجية حقيقية، وقد تشتمل على مشاريع بسيطة في بعض الجامعات. ويَتعلّم الطالب في هذه المادة عددًا من التقنيات والأدوات الشائعة لتوصيف الأنظمة وتصميماتها، مثل لغة النمذجة الموحدة Unified Modeling Language واختصارًا UML وآلات الحالة State Machines وغيرها من التقنيات. 2. الرياضيات الأساسية مادة الرياضيات مادةٌ أساسيةٌ لدى جميع طلاب الهندسة عمومًا؛ لذلك من الضروري أن يُدرك الطالب أساسيات الرياضيات مثل العمليات الحسابية والتفاضل والتكامل والجبر وحسابات الأشكال الهندسية وغير ذلك من العمليات الرياضية البسيطة. ولا تُعلّم هذه المادة في الجامعات بهدف الرياضيات ذاتها؛ فبمجرد انتهائك من المادة ستنسى غالبًا تلك المعادلات الرياضية، ولكن الهدف هو تعويد الطالب على استخدام العقل لحساب ما لم يقدِر على حسابه من قبل، فمواجهة القليل من الصعوبة في التَعلّم تُفتّح الذهن وتُعوّده على تحمّل الأكثر صعوبةً من هذه الرياضيات البسيطة مستقبلًا. 3. الرياضيات الخطية ستتعلم في الرياضيات الخطية أو الجبر الخطي أساس حلّ المعادلات الرياضية، مثل معادلات الدرجة الأولى والثانية والثالثة، وبعض الأساليب والقوانين الرياضية لحلها بسهولة. كما ستتعلم بعض الأمور عن المصفوفات وطريقة التعامل معها وإجراء بعض الحسابات لها وطريقة الاستفادة منها مثل المصفوفات أولية وثنائية وثلاثية الأبعاد. 4. التفاضل والتكامل هنا ستتعمق في التفاضل والتكامل بعد اطلاعك عليها بصورة مبسطة بالفعل في إحدى المواد الرياضية السابقة، وستتعرف عليهما بالتفصيل، وكيف تستخدمها لحل بعض المشكلات الحقيقية في العالم، كما ستعرف بعض قواعد التفاضل والتكامل. وقد تكون هذه المادة متقدمة على بعض الطلاب مثل أغلب مواد الرياضيات، وذلك لأن الطالب يتعلم فيها المعادلات والقواعد والقوانين دون أن يُعلّم من أين جاءت ولا كيف ولماذا سيتعلمها ولا أين سيستخدمها في حياته المستقبلية. 5. تصميم المنطق الرقمي ستتعلم في مادة تصميم المنطق الرقمي Digital Logic Design أهم أركان التفكير المنطقي، وكيفية تمثيل الحجج والنقاشات والمعضلات في العالم الحقيقي على شكل معادلات رياضية ومنطقية وكذلك قد تتعلّم بعض الأمور عن المجموعات Sets في الرياضيات وطريقة استخدامها لتمثيل معلومة أو بيانات معينة. كما ستتعلم تصميم البوابات المنطقية Logic Gates مثل and و or و nor و xor و not وغيرها، وكذلك ستفهم الطريقة العامة لعمل المعالجات داخل الحواسيب والأجهزة الإلكترونية. وهذه المادة واحدةٌ من بين أكثر المواد إفادةً ضمن علم الحاسوب لأن طريقة التفكير المنطقية هذه ستنقلك من إنسان عشوائي التفكير قد لا يفكر بمنطقية في معظم أمور حياته إلى إنسان يفهم الحياة بصورة منطقية كما تفهمها الآلات ويَسهُل عليه تحديد ما هو منطقي وما هو غير منطقيٍ من الحجج والمعلومات التي حوله، كما ستفهم كيفية تحويل الأفكار إلى أنظمة فيزيائية وبرمجية داخل الحواسيب بحيث تُستعمَل لإكمال تصميم الحواسيب وتُجري العمليات الحسابية المطلوبة. أي الغرض من هذه المادة أن ترى كيف يعمل الحاسوب وكيف يفهم الأوامر وينفذها حتى تعرف كيف ستتعامل معه وتبرمجه وتتخاطب معه بسهولة. وفقًا لما سبق، من الأفضل التركيز عليها كونها واحدةٌ من أهم المواد في المجال، وقد تُدرّس هذه المادة على عدة مواد منفصلة، لذلك قد تجد موادًا شبيهةً مثل التفكير المنطقي Critical Thinking والرياضيات المتقطعة Discrete Mathematics، فكلّ على حسب الجامعة والتخصص. 6. الاحتمالات مادة الرياضيات الأخيرة التي ستتعلّمها هي مادة الاحتمالات Probabilties أو نظرية الاحتمالات Probability Theory، وهي مادةٌ تهدف إلى تعليمك أهم مبادئ هذا العلم بالإضافة لأنواع الاحتمالات المُمكنة حسب الأحداث Events وطريقة ارتباطها ببعضها البعض ليُستفاد منها في الحياة الواقعية إما في اتخاذ قرارات المؤسسات أو إجراءات أصغر من ذلك سواءٌ على مستوى الأفراد أو الأجهزة الرقمية. والاحتمالات علم عميق تقوم عليه الكثير من الرياضيات الحديثة، كما يقوم عليه علم التعمية Cryptography المُستخدم في تأمين وتشفير الاتصالات والشبكات، وستستفيد من هذه المادة كثيرًا في المستقبل متى أتقنتها. 7. البرمجة من البداهة أن توجد مادة مختصة بتعلم البرمجة في تخصص علوم الحاسب أو التخصصات المرتبطة به، وذلك لأن البرمجة هي لبّ علوم الحاسب في النهاية، فجميع الأجهزة والأنظمة التي حولنا ما هي إلا برمجيات وخوارزميات مختلفة صنعت لتعمل بطريقة معيّنة. كانت لغة جافا Java أبرز لغة برمجةٍ تُعلّم في الجامعات قبل عقد من الزمن، بينما تميل الكفة الآن إلى بايثون Python أو جافاسكربت في بعض الأحيان، ولكن بايثون هي الأكثر استعمالًا واعتمادًا في الجامعات لما لها من تطبيقات لاحقة في جميع المجالات الأكاديمية الأخرى. وفي كل الأحوال ستتعلّم في هذه المادة المتغيرات والخوارزميات وحلقات التكرار والجمل الشرطية والبرمجة كائنية التوجه وغيرها من المبادئ البرمجية، وذلك بِغَض النظر عن لغة البرمجة التي ستتعلّمها. كما قد تُعلّم القليل من استخدام بيئة ماتلاب Matlab وهي بيئة برمجية ولغة برمجة تُستخدَم لإنشاء الحسابات الرياضية وبرمجة برمجيات معينة، لذلك فهي منصة ولغة برمجة في آنٍ واحدٍ، ويستعمل ماتلاب طلاب أقسام الهندسة الأخرى لمحاكاة وحساب المعادلات والنماذج الرياضية. 8. الخوارزميات وهياكل البيانات الخوارزميات Algorithms وهي الإجراءات المنطقية المتسلسلة لحلّ مشكلة ما، وسنتعلّم الخوارزميات وأفضل الطرق لتصميمها من أجل حل مختلف المشكلات التي قد تظهر لنا في المستقبل، حيث سنحتاج إلى معرفة متى نستعمل خوارزمية بعينها من أجل حل مشكلة ما، ومتى سنلجأ لخوارزمية أخرى نعرف أنها ستعطينا أداءً أفضل، بمعنى أننا سنغوص في الموازنة بين مختلف الخوارزميات وأدائها مثل السرعة والوقت ومساحة التخزين، ومتى يجب استعمال كلّ واحدةٍ منها كما سنطّلع على مختلف تشعيبات الخوارزميات المتوفرة. هياكل البيانات Data Structures وهي طريقة تخزين البيانات واستعمالها بغرض تحقيق أهدافٍ مختلفةٍ حسبما يريد المُطوّر؛ فهناك هياكل بياناتٍ أسهل وأوضح للفهم العام، وهناك هياكل بيانات أسرع في الأداء، وهناك هياكل بيانات غير سريعة في الأداء لكنها تستهلك حجمًا أقل في تخزين البيانات وبالتالي قد تفضل على غيرها في بعض الأحيان. وستتعلم كل هذه الأمور في هذه المادة، وربما تقسم هذه المادة إلى مادتين على حسب الجامعة والاختصاص الذي تدرسه، كما ستطلع على أنماط التصميم Design Patterns إذا كنت ستدرس هندسة البرمجيات؛ وهي الأنماط الشهيرة لتصميم البرمجيات بحيث لا تحتاج لإعادة اختراع العجلة مرة أخرى من أجل كل مشكلة تواجهها. 9. مبادئ أنظمة التشغيل أنظمة التشغيل هي البرمجيات الأساسية التي تُدير الأجهزة الحاسوبية والهواتف والأنظمة المدمجة وغيرها، حيث توزع الموارد على البرمجيات وتدير تشغيلها وعملها ومراقبتها للمستخدم، وأبرز أنظمة التشغيل على سطح المكتب هي ويندوز Windows وماك Mac ولينكس Linux، أم فيما يتعلق بأنظمة تشغيل الهواتف الذكية، فلدينا نظام أندرويد Android الذي يعمل على مختلف الأجهزة ونظام iOS الذي يعمل على أجهزة آبل Apple فقط. وستتعلم في هذه المادة أساسيات عمل أنظمة التشغيل المختلفة، بالإضافة لأبرز المبادئ التي تعمل بها أنظمة التشغيل الحالية مثل العمليات وأجهزة الإدخال والإخراج Input/Output وإدارة الذاكرة والوصول للمعالِج ونظام الوصول والصلاحيات وإدارة المستخدمين وغير ذلك. وستضع عليك معظم الجامعات واجبات منزلية بلغة سي C في هذه المادة، لذلك سيكون عليك تَعلّمها إن أردت اجتياز هذه المادة بنجاح. 10. مبادئ الشبكات الشبكات هي كذلك من أبرز سمات العصر الحديث فشبكات الاتصال الخلوية وشبكة الإنترنت والشبكات المنزلية كلها غيّرت شكل العالم الحديث وساهمت في فوائد جمة لا تعد ولا تحصى فبدون علم الشبكات، لن تتمكن من قراءة هذا المقال الآن. وستتعلم في هذه المادة أساسيات الشبكات وطريقة عملها، وستطّلع على بروتوكولات التواصل أشهرها بروتوكول TCP وبروتوكول UDP، كما ستطّلع على بعض المفاهيم والأجهزة الأخرى مثل الموجه Router والخوادم Servers وأجهزة العملاء Clients والنظير للنظير Peer to Peer وغير ذلك من مبادئ الشبكات البسيطة. وسيطلب منك غالبًا برمجة برنامج محادثة بسيط بأي لغة برمجةٍ تريدها وذلك باستخدام أحد بروتوكولات التواصل، وهو ما سيوضح مدى فهمك وتعلمك لهذا المساق. 11. معمارية الحاسوب إلى هنا سيكون طالب علم الحاسوب قد فهم بالفعل معظم الأساسيات التي سيحتاج إليها، ولكن مادة معمارية الحاسوب أو بنية الحاسوب Computer Architecture ستتعمق في تفاصيل دقيقة حول مكونات الحاسوب الفيزيائية وكيفية تواصلها مع بعضها بعضًا مثل طريقة تواصل الذاكرة العشوائية مع المعالِج وقرص التخزين وطريقة إرسال أو جلب المعلومات منهما. كما سيطّلع على مفهوم المسجلات Registers، وهي وحدات الذاكرة الصغيرة للمعالجات الحديثة، وكيف يستعملها لتسريع عمليات المعالجة بدلًا من جلب البيانات من الذاكرة، وسيفهم كيف ستتحول الخوارزميات والبرمجيات التي يكتبها إلى عمليات مجزأة يمكن للمعالجات حسابها في أقل من لحظة من الزمن بفضل البوابات المنطقية التي تعرّف عليها مسبقًا. أي أن هذه المادة ستغوص في تفاصيل عمل أجهزة الحواسيب وبنيتها الداخلية وطريقة تواصل مكوناتها مع بعضها بعضًا. زيادةً على المواد الأساسية التي ذكرناها آنفًا، هناك مواد ومجالات وتخصصات اختيارية يختار الطالب غالبًا ما يحلو له منها ليمارس المجال الذي يحبه، ولا يتخرج مهندسو الحاسوب وهم يتقنون كل هذه التخصصات، فعادةً ما يختارون واحدًا أو اثنين منها على الأكثر، ثم يدرسونه بصورةٍ أعمق من السابق، لذا سننتقل تاليًا إلى شرح تخصصات الحاسب التي يمكن لمهندس الحاسوب أو دارس تخصص علم الحاسوب أن يعمل ويتخصص فيها. تخصصات علوم الحاسب لا يكون هناك تخصصات واضحة في الجامعة أحيانًا، بل يمكن للطالب ببساطة أن يختار مجال أحد المواد التي درسها وأحبها ويتخصص فيها إلى حين إتمام السنوات الدراسية. سنعرض أهم تخصصات الحاسب والمجالات التي يمكن العمل فيها بعد التخرج: 1. البرمجة وهندسة البرمجيات هندسة البرمجيات وهي العلم المَعني بصناعة البرمجيات بطريقة احترافية، فهي لا تختص بالبرمجة فقط وإنما تشمل فروعًا وعمليات أكثر من ذلك بكثير. صحيحٌ أن تطوير تطبيقات سطح المكتب وتطوير تطبيقات الهواتف المحمولة هي المجالات البرمجية الأكثر شيوعًا، لكن هناك مجالات برمجية أخرى أكثر تعقيدًا مثل: برمجة أنظمة إدارة الخوادم Servers مثل Kubernetes ودوكر Docker وأشباهها من أدوات إدارة العمليات DevOps. برمجة أدوات الأنظمة المالية والاقتصادية التي تدير اقتصادات الدول والبنوك حول العالم. برمجة أنظمة الشركات العملاقة مثل شركات الطيران وشركات النفط وشركات الكهرباء …إلخ، فهذه أنظمةٌ حساسةٌ لا تحتمل الفشل وإن احتملته فهي بحاجة للاسترجاع والإدارة الفورية. الكثير من الشركات العملاقة كانت تعمل قبل عِدّة عقودٍ من الزمن على أنظمة مبرمجة بلغات برمجية قديمة عفا عليها الزمن الآن ولم يعد يُبرمَج بها، ولكن ما زالت هذه الشركات تعمل بتلك الأنظمة لعِدّة أسبابٍ لوجستيةٍ وهنا يعد نقل البرمجيات من لغة برمجة معينة إلى لغة برمجة أخرى هو مجال تخصصي كبير في علوم الحاسبات وقسم هندسة البرمجيات، فمثلًا تدفع شركة IBM رواتب عملاقة لمن يجيد نقل برمجياتٍ مكتوبةٍ بلغة COBOL إلى لغة C. ويقوم مهندسو البرمجيات بالتخطيط لعملية بناء البرمجيات اللازمة لهذه المجالات، كما يخططون للموارد التي ستلزمهم والمميزات التي سيسلمونها بالإضافة إلى نمط التصميم الذي سيستعملونه في مختلف مراحل تطوير البرنامج، ولذلك هي عملية معقدة واحترافية تحتاج إلى فريق متكامل من المبرمجين وليست مُجرّد برمجة بسيطة ينجزها فرد واحد. 2. هندسة الشبكات الشبكات ليست برامج التواصل بين المستخدمين فقط، فهناك شبكات أعقد من ذلك بكثير. ويمكنك التخصص في مجال الشبكات لتفهم طريقة عمل الشبكات البسيطة وحتى العملاقة مثل شبكة الإنترنت، والبنية التحتية لها وطريقة ربط الدول لتلك البنية التحتية، كما يمكنك التعرف على بعض أنماط الشبكات البديلة مثل النظير للنظير Peer to Peer وكيفية عمل تطبيقات مفيدة بها في الحياة الواقعية. وكذلك قد تتطرق إلى الشبكات المركبية Vehicular Networks وهي الشبكات المرتبطة بالسيارات والمركبات، أو شبكات الاتصال الخلوي وطريقة صنعها وعملها، وطريقة التعامل مع مختلف أجهزة المودم Modems. وبشكلٍ أبسط من ذلك، فيمكن أن تعمل مدير شبكات في المؤسسات والشركات الصغيرة والكبيرة وحتى مراكز إدارة الخوادم Data Centers، فلا تخلو الآن مؤسسة أو شركة من شبكة داخلية تربط أقسامها مع بعضها وهي آنذاك بحاجة إلى مدير شبكة بالتأكيد هذا أقل القليل. 3. الذكاء الاصطناعي وتعلم الآلة زاد الاهتمام بالذكاء الاصطناعي زيادة كبيرة في العقد الأخير، وهذا أمرٌ طبيعيٌ بسبب زيادة الحاجة إليه، بالإضافة إلى تَوفُّر الموارد الحاسوبية الكافية لتشغيل خوارزمياته المختلفة على أجهزة المستخدمين الشخصية، فدخل الذكاء الاصطناعي في كل شيء حولنا من إدارة إشارات المرور وتنظيم السير وتصنيف الصور في هواتفنا والتعرف على الأشخاص والوجوه والترجمة الآلية وإدارة المركبات والآلات وكشف الجرائم وعمليات التتبع وتحليل البيانات والكثير الكثير غيرها حتى بات تقريبًا عصب التقدم والتطور الذي نشهده حاليًا، وهو الأمر الذي يفسر سبب الطلب الكبير على المتخصصين في هذا المجال كما أشرنا. وهناك فرق بين الذكاء الاصطناعي Artificial Intelligence وتعلم الآلة Machine Learning؛ فالأول هو اسم المجال العام المَعني بكل ما يتعلق بإنشاء ذكاء يشابه ذكاء الإنسان باستعمال الآلات بينما الثاني مجال فرعي يتعلق بتدريب الآلات على مجموعة بيانات معينة بهدف الخروج منها بتصنيفات يمكن أن تساعد الآلة على اتخاذ قرار مطلوب منها. وستتعلم في هذا التخصص فروع تعلم الآلة الثلاثة الأشهر وهم، التعلم الموجه Supervised Learning والتعلم غير الموجه Unsupervised Learning بالإضافة إلى التعلم المعزز Reinforcement Learning، وللمزيد من المعلومات عن المجال، يمكنك الاطّلاع على كتاب مدخل إلى الذكاء الاصطناعي وتعلم الآلة. 4. الحوسبة البيولوجية الطبية لا تأتي الأجهزة الطبية الموجودة بالمستشفيات من العدم، بل يصنعها المهندسون ويساهم فيها المبرمجون، وهناك مجموعة من المواد الجامعية في تخصصات الحاسب التي تعلم الحوسبة البيولوجية الطبية الحيوية Biomedical Computing، وقد تكون تخصصًا كاملًا منفصلًا في بعض الجامعات باسم هندسة طبية حيوية biomedical engineering أو هندسة الأجهزة الطبية أو الهندسة الطبية. ويمكن أن تتخصص في هذا المجال لتتعلم أهم المتطلبات البرمجية في المجال الطبي أو البيولوجي، وكيفية تطوير برمجيات أو قطع عتاد تحل مشاكل أو تقدم استخدامات حقيقية تفيد مجال الطب والعلاج أي تقريبًا هو تخصص في مجال تصنيع الأجهزة المتعلقة في المجال الطبي من تصميم وبرمجة واختبار وتنفيذ …إلخ. ولا تستفيد المستشفيات فقط من هذا التخصص بل تستفيد كذلك مختبرات التحليل والمختبرات الحيوية والمستوصفات والعيادات الطبية وغيرها، فتحتاج جميعها إلى برمجيات صادرة عن المتخصصين في هذا المجال. 5. الرسوميات لا تُعَد الرسوميات Graphics مجالًا واحدًا فقط، فهو مجال تطوير عريض جدًا، حيث تنضوي تحته مجموعة من المجالات المتفرعة، نذكر من بينها: أنظمة التشغيل، فلها مكتبات برمجية معينة تساعدها على عَرْض الخطوط والصور والواجهات الرسومية وغير ذلك للمستخدمين، وهذا مجال ضخم، فهو ضروري لتمكين المبرمجين الآخرين من تطوير التطبيقات والألعاب لأنظمة التشغيل هذه، أي أن الرسوميات هنا هي جزء من نظام التشغيل. محركات الرسوميات Graphics Engines، وهي أنظمة برمجية عملاقة تهدف لتمكين المبرمجين من برمجة مختلف الرسوميات التي يريدونها لمختلف أنظمة التشغيل، فمثلًا يستخدم محرك Unity 3D أو Source2 أو غيرهما لبرمجة الألعاب التي تعمل على مختلف أنظمة التشغيل مثل ويندوز وماك ولينكس وأندرويد. البرامج المتخصصة في الرسم ثنائي وثلاثي الأبعاد، فتعد برمجة هذه البرمجيات مجال منفصل ويتطلب بعض الخبرات والمهارات، بينما تعلم استخدامها سيحولك إلى مصمم، وتدخل فيها برامج النمذجة modeling مثل برامج الرسم والتصميم الهندسي CAD المفيدة في الكثير من التخصصات الهندسية. برامج تحرير ومونتاج الفيديوهات مجال فرعي آخر مشهور. وتدخل الكثير من التخصصات تحت تخصص الرسوميات كما ترى، إلا أنها قد تختلف جذريًا فيما بينها، فتطوير برامج الرسم ثنائية الأبعاد لا يشبه تطوير المحركات الرسومية فعملية تطوير المحركات الرسومية واحدة من أصعب العمليات البرمجية وأكثرها تعقيدًا، لأنها تحاكي فيزياء الكون وتتطلّب فهمًا عميقًا للرياضيات. 6. تحليل البيانات وإدارة قواعد البيانات مجال تحليل البيانات (أو يرقى حتى إلى قسم منفصل باسم هندسة تحليل البيانات data analysis engineering) واحد من أكثر المجالات طلبًا حاليًا، لما له من استخدامات مفيدة وجلية في تحليل البيانات والمعطيات واستعمالها في اتخاذ القرارات المُهمَّة لدى المؤسسات والشركات، حتى إن بعض الفرق الرياضية لكرة القدم مثل ليفربول تعتمد على تحليل البيانات لوضع خططها الرياضية في كل مباراة، فتطبيقات هذا المجال واسعةٌ جدًا. كما ستتعلم في هذا المجال طريقة معالجة مجموعات ضخمة من البيانات، وطريقة تنظيفها من الأخطاء والمشاكل، وتحويلها إلى بياناتٍ يمكن معالجتها من الأنظمة الحاسوبية، وستتعلم طريقة استخراج أهم المعلومات والأسرار المخفية في البيانات الخام التي لديك بمختلف المكتبات واللغات البرمجية. حتى تتقن فن التعامل مع البيانات، فيجب أن تتقن التعامل مع أنظمة قواعد البيانات التي تخزَّن فيها البيانات، لذا يتخصص هذا المجال في مفهوم قواعد البيانات Databases وأنظمة إدارة قواعد البيانات database management systems تختصر إلى DBMS بتعمق كبير فهي مدخل إلى التعامل مع البيانات والتلاعب بها وتطويها والغوص فيها لاستخراج المعلومات. قد يتفرد هذا المجال في تخصص بمفرده في الدراسات العليا أو حتى في سوق العمل، فقد تطلب بعض الشركات متخصص لإدارة قواعد بياناتها وله المسمى الوظيفي "مدير قواعد بيانات" Database Administrator. وتشمل مشاريع تحليل البيانات عادة: جمْع البيانات من مصادرها. تنظيف وتهيئة البيانات للمعالجة وإزالة القيم الشاذة. إدخال البيانات في الأنظمة الحاسوبية المطلوبة لاستخراج النتائج والمعلومات الأولية منها، ومن أين يُبدَأ في البحث عن روابط مفيدة بين أجزائها. تحليل أعمق للبيانات إما عبر أنظمة أكثر تقدمًا أو عبر تَعلّم الآلة، وقد تُستعمل أنظمة التعرف على الأنماط Pattern Recognition كذلك. تهيئة النتائج السابقة للعرض والمشاركة عبر بعض الأدوات الشهيرة. عرض النتائج والتقارير مع المخططات والتوصيات. وتعد بايثون أكثر لغات البرمجة طلبًا في هذا المجال، بالإضافة لمكتباتها الشهيرة مثل Pandas وNumpy وJupyter Notebook وMatplotlib وغيرها، وكذلك تستعمل لغة R في هذا المجال، ولكنها ليست بنفس شيوع بايثون، أما بالنسبة لقواعد البيانات، فيجب أن تقن لغة SQL وتتقن بعدها أنظمة التعامل مع قواعد البيانات. ولتَتعلّم أساسيات بايثون بسهولة، بإمكانك الاطّلاع على كتاب البرمجة بلغة بايثون كما يمكنك الاطلاع على كتاب ملاحظات للعاملين بلغة SQL وكتاب الدليل العملي إلى قواعد بيانات PostgreSQL. 7. أنظمة التشغيل والأنظمة المدمجة تحتاج جميع الحواسيب والهواتف الذكية والأجهزة الإلكترونية عمومًا إلى نظام تشغيل Operating System لتعمل وفق رغبات المستخدم، وهذه الأنظمة هي الأخرى لا تأتي من العدم بل هناك متخصصون لبرمجتها وجعلها أفضل من غيرها والحفاظ عليها مع الزمن. تطوير أنظمة تشغيل سطح المكتب مثل ويندوز وماك ولينكس، أو أنظمة الهواتف الذكية مثل نظام أندرويد ونظام iOS وغيرهما، هي مجالات برمجية متقدمة تعتمد على خبرات ومهارات متعددة، كما يعتمد هذا على العتاد Hardware الذي ترغب ببناء نظام تشغيل له، وقد تُستخدَم أكثر من لغة برمجية في الوقت نفسه لتطوير نظام تشغيل بسيط، فتطوير أنظمة التشغيل عملية عملاقة ومكلفة وتحتاج الكثير من المهندسين، كما ينفق عليها ملايين الدولارات من الاستثمار المستمر على امتداد عدة سنوات للخروج بنتائج مرضية، ولهذا ربما تجد أن هناك ملايين البرمجيات الصغيرة حول العالم، بينما يوجد ثلاث أنظمة شهيرة لسطح المكتب ونظامان للهواتف الذكية فقط، ورغم أنه هناك بالفعل العشرات غيرها إلا أنها محدودة وضعيفة المزايا موازنة بتلك الأكثر استخدامًا والأكبر دعمًا حاليًا؛ وذلك لأن مطوريها لا يمتلكون نفس موارد وإمكانات مطوري الأنظمة الشائعة ذات الدعم الكبير. أما الأنظمة المدمجة Embedded Systems فهي الأنظمة التي تعمل مع موارد عتاد Hardware Resources محدودة للغاية، فهي تُنفّذ مهامًا محدودةً وصغيرةً مثل أنظمة الإشارات والاستشعار والأقفال الإلكترونية وألعاب الأطفال وحتى أنظمة إدارة السدود المائية وأنظمة الأجهزة العسكرية …إلخ، كما أن برمجتها مجالٌ منفصلٌ لوحده، ويوجد به الكثير من التفرعات كذلك وهو علم ضخم ذاع صيته هذه الأيام وأهم مجال تفرع عنه هو مجال إنترنت الأشياء Internet of Things تختصر إلى IoT وهي برمجة العتاد مع ربطه بالإنترنت مثل أنظمة مراقبة المباني وأقفالها وأنظمة البيوت الذكية وغيرها. أصبحت هنالك برامج واختصاصات دراسات عليا وحتى درجات جامعية تُدرِّس هذا المجال. 8. الأمان الرقمي الأمان الرقمي مجال كبير تنضوي تحته عدة أمور، مثل أنظمة التشغيل والبرمجيات العادية والشبكات والتعمية وغيرها، فكلها أمور مترابطة تحتاج لتأمين واستخدام أفضل للتقنيات وذلك للتأكد من عدم وصول الأشخاص غير المخوّلين لها. وبينما هناك مجالاتٌ متعلقةٌ بالأمان الرقمي الشخصي للمستخدمين، هناك مجالاتٌ متعلقةٌ بالأمان الرقمي الجنائي أو العسكري بين المجرمين وأجهزة الشرطة وبين الدول وبعضها البعض، كما أن هناك مجالاتٌ متعلقةٌ بالشبكات وإدارة الخوادم وغير ذلك الكثير، وكلها أمورٌ يمكن أن تتخصص بها إذا رغبت بذلك. ولمعلومات مفيدة حول الأمان الرقمي الشخصي والخصوصية، اطلع على كتاب دليل الأمان الرقمي. 9. الحوسبة العلمية تعتمد العديد من الصناعات التي تراها حولك اعتمادًا أساسيًا على علم الحاسوب لإتمام مهامها بنجاحٍ، فأغلب ما تراه حولك قد عولج في الحاسوب بدءًا من البناء الذي تقطنه والذي قد صمم واختُبر باستعمال برامج حاسوبية صممت البناء ونمذجته واختبرته على الزلازل ومختلف العوامل للتأكد منه ومن تصميمه وحتى قطع وقطع البلاستيك وأجزاءها قد صممت أيضًا باستعمال برامج حاسوبية عملاقة وهكذا، وكل هذه البرامج تعمل بخوارزميات ضخمة تحتاج للكثير من العمليات الرياضية التي تحل معادلات مختلفة، وهي بدورها تعتمد على خوارزميات محددة لتبسيطها وحسابها وهذا ما يدخل ضمن مجال الحوسبة العلمية Scientific computing. كما توجد مجالات كثيرة أخرى مثل هذه لا مجال لحصرها الآن ولكن نذكر منها مجال الحوسبة التطورية Evolutionary Computation وهي العلم الذي يأخذ نظرية التطور والحالات المعقدة المتطورة ويحولها إلى نظام قابل لحل الكثير من المشاكل في الحياة الواقعية، فيمكن استخدام هيكلة ومبادئ نظرية التطور لتطوير خوارزمية تساعدنا على معرفة عدد المطارات والمدارج التي علينا وضعها في كل مدينة. 10. الحوسبة السحابية توفر الآن الكثير من الشركات خدمات سحابية Cloud Services للمستخدمين وفكرة الحوسبة السحابية Cloud computing ببساطة إلغاء العمل على نظام تشغيل سطح المكتب لتخزين وتشغيل وإدارة الخدمات والبرامج التي تريدها والاعتماد كليًا على خدمات بعيدة موجودة على حواسيب وخوادم مركزية عملاقة تستفيد من الإنترنت للوصول إليها لفعل نفس الأمور السابقة. فمثلًا بدلًا من تشغيل خادم قاعدة البيانات الخاص بك يمكنك الاعتماد على أحد الحلول الجاهزة من أمازون Amazon أو ديجيتال أوشين Digital ocean، كما يمكنك استعمال فيجوال ستوديو من مايكروسوفت عبر الإنترنت لأداء مهامك البرمجية بدلًا من استخدام مُحرر النصوص البرمجي الخاص بك على نظامك. ويمكنك كذلك أن تستأجر خوادم فيزيائية Dedicated Servers ثم تُقسّمها إلى خوادم افتراضيةٍ Virtual Servers وتُوزّعها على مستخدمين آخرين حسب الموارد، ثم تُوّفر لهم خدمات جاهزة Software-as-a-Service واختصارًا SaaS، وهذا هو مبدأ شركات الاستضافة الحديثة والمجال السائد فكل ما تراه أصبح مخزنًا وموجودًا على سحابة وتصل إليه أو تستعمله عبر الإنترنت لذلك زاد الطلب على متخصصين في هذا المجال وتكون المسميات الوظيفية باسمه عادة مهندس حوسبة سحابية Cloud Computing Engineer. 11. تخصصات أخرى هناك مجالات ضمن علم الحاسوب تبتعد عن العلوم التطبيقية البرمجية، وتبحث في المبادئ النظرية لما تقدمه علوم الحاسب للتخصصات والمجالات الأخرى في الحياة. نظرية المعلومات Information Theory وتتحدث عن المبادئ النظرية لنقل المعلومات الرقمية ومحتواها، وإلى أي مدى يمكن ضغطها وبأي سرعاتٍ يمكن الوصول إليها. نظرية الإشارات Signal Theory وتتحدث عن الإشارات التي تُصدِرها الكائنات الحية وكيف يمكن تحديد العلاقات بينها وكيفية نمذجتها في الأنظمة الرقمية بهدف دراستها وتحليلها. نظرية الفوضى Chaos Theory ورغم أنها نظريةٌ رياضيةٌ لا تنضوي تحت علوم الحاسوب بصورةٍ مباشرةٍ، إلا أن لها تطبيقاتٌ واضحةٌ في عِدّة مجالاتٍ مثل حالة الطقس والمناخ والأنظمة المصممة لتوقّعها والتبليغ عنها، فهي تعتمد على علوم حاسوبية مثل التعمية والروبوتات وغيرها. علم التعمية Cryptography وهو واحدٌ من أكثر العلوم أهمية في علوم الحاسب لأن تطبيقاته هي التي تسمح بتشفير البيانات، ويعتمد على المبادئ الرياضية بشدةٍ، كما أنه شديد التعقيد، حيث تقوم عليه أنظمة الأمان الرقمية حول العالم بما في ذلك الأنظمة الاقتصادية. والكثير من هذه العلوم هي علوم نظرية تعتمد على مجالات متداخلة Interdisciplinary مع علوم الحاسبات، إلا أن علوم الحاسبات مركزية وأساسية فيها. كيف أختار التخصص المناسب لي من تخصصات علوم الحاسب؟ ليست كل التخصصات السابقة الذكر متوفرة في مجالات علوم الحاسب بالطبع، فهناك غيرها ولكن ذكرنا الأشيع والأشهر منها، ويبرز هنا سؤال وهو كيف يمكن لداخل جديد إلى هذه العلوم أن يختار التخصص المناسب له والذي يحبه؟ وتكمن الإجابة في معرفة المرء بنفسه وأين يحب أن يعمل فإذا كنت تحب الرياضيات فربما تناسبك العلوم النظرية، وإذا كنت تحب العمل مع الخوارزميات وتطوير الجديد منها لتسريع حل مشكلات الجنس البشري وابتكار حلول أفضل فربما قد يكون هذا اختصاصك، أو ربما ببساطة تحب تطوير البرمجيات والتطبيقات وحينها لن تحتاج شيئًا أكثر من المعارف الأساسية في علوم الحاسب ثم التخصص في تعلم البرمجة. لذا عليك أن تنظر في هذه المجالات المختلفة، ثم تسأل نفسك في أي منها يمكن أن تعمل دون أن تشعر بالملل بعد فترة؟ وستجد بضع مجالات قد تعجبك وستستبعد بعض المجالات الأخرى، فمثلًا أنا أستبعد أن أعمل في المجالات الرياضية أو مجالات التشفير، ببساطة لأنني لا أحب تعقيدها، وبعدها حاول أن تصرف بعض الساعات من وقتك في هذه المجالات التي تبقّت معك لتتعرّف عليها وترى أبرز المشاريع الموجودة فيها، وهل أحسست براحة وانشراح للنفس أثناء دراستها والعمل فيها، أم أنك تريد غيرها؟ الخلاصة، جرب التخصص والقراءة الدراسة في كل تخصص فترة من الزمن وبعدها إما أن تكمل أن تنتقل إلى تخصص آخر وهكذا تعرف الاختصاص الصحيح المناسب لك، وبعد فترة ستجد مجالات واضحة معينة لن تحب العمل في غيرها، وتلك هي المجالات التي يمكنك البدء بدراستها والتخصص بها بعد أن تنتهي من دراسة علوم الحاسوب الأساسية. التوظيف وفرص العمل في مجال علوم الحاسب تقل أهمية الشهادة الجامعية تدريجيًا في التوظيف عندما يتعلق الأمر بعلوم الحاسوب فشركات مثل جوجل بدأت تهتم بالخبرة وتعمل على نشر شهاداتها الخاصة والتي تبلغ مدتها 6 أشهر في حال عدم وجود الشهادة الجامعية، كما أن كثيرًا من الشركات الأخرى تشترط وجود الخبرة ومعرض الأعمال بدلًا من الشهادة الجامعية للمتقدِّم. وتبلغ مدة أغلب الشهادات الجامعية في معظم البلدان 4 سنوات، وهي مدةٌ طويلةُ جدًا يذهب معظمها بالانتظار والفراغ بين الحصص الدراسية الأسبوعية وتعلم علوم ومواد قد لا يكون لها أهمية كبيرة وقد تنساها بعد التخرج هذا لم نتحدث عن قلة المقاعد الدراسية الحكومية المجانية والمنافسة الشديدة عليها أو غلاء الجامعات الخاصة مما يزيد من العوامل التي تعيق أصلًا دخول الجامعة أو اختيار التخصص الذي ترغب دراسته في الجامعة فقد تضطر من أجل الدخول للجامعة والحصول على درجة جامعية أن تدرس تخصصًا لا تحبه ولا يناسبك أصلًا وهنا الحديث يطول، على أي حال، وبما أن علوم الحاسوب مجال مرتبط بالحواسيب التي بين أيدينا فقد لجأت نسبة لا بأس بها ممن يرغبون التخصص في المجال بتعلم المعلومات المطلوبة من مصادر خارجية لا علاقة لها بالجامعات الأكاديمية، دون الحاجة للدراسة الجامعية التقليدية ودخلوا سوق العمل فعليًا. ولا نريد محو أهمية الشهادات الجامعية، وذلك لأنها ما تزال تطلبها الحكومات والدول رسميًا للعمل في الوظائف الحكومية، كما لا تزال لها سمعة في الأوساط الاجتماعية على عكس من يتعلم تلك العلوم بمفرده، ولكن إذا كانت المعرفة والعمل ما يهمك فقط فحينها لا مشكلة من الخوض في المجال بعيدًا عن الدراسة الجامعية، وبذلك تكسب سنوات من عمرك بالإضافة إلى توفير الكثير من المال الذي كنت ستصرفه على الشهادة الجامعية. فمثلًا، يمكنك خلال فترة تترواح من ستة أشهر إلى سنة أن تتعلم أساسيات علوم الحاسوب وأن تبدأ العمل فيها مثلًا أن تتخصص في البرمجة وتطوير المواقع وتدخل سوق العمل وأغلب الدورات الحرة التي تعلم الاختصاص تكون مدتها ستة أشهر وحتى السنة وهي كافية لوضع قدمك على الطريق ودخول وكسب فرصة عمل. فمؤسسي أكبر الشركات التقنية في العصر الحديث، مثل مارك زوكيربيرغ مؤسس فيسبوك، وجاك دورسي مؤسس تويتر، وستيف جوبز مؤسس آبل لم يتخرجوا من الجامعات، وكذلك فهناك غيرهم الكثير من مدراء ورؤساء الشركات التقنية، ولهذا لن تكون الشهادة الجامعية عائقًا أمامك في حال أردت سلوك نفس المسار العصامي. وهنالك الكثير من المبرمجين الماهرين اليوم الذين لم يدخلوا إلى الجامعة أو درسوا اختصاصًا مختلفًا وهم يعملون في شركات كبيرة منها شركة IBM وهو لا يملك درجة في أي تخصص من تخصصات الحاسب وحتى أنه يساهم في كتابة بحث مع من درس في أروقة الجامعات. أضف إلى ذلك أنه يمكنك العمل كعامل مستقل على حسب الاختصاص الذي تجيده من اختصاصات علوم الحاسوب؛ فلو كنت مطور ويب محترف فيمكنك تطوير المواقع الإلكترونية للعملاء عبر مواقع العمل الحر مثل مستقل، ففي العمل الحر لن يسألك أحد بتاتًا عن شهادتك الجامعية وكل ما سيسألونك عنه هو خبراتك ونماذج لأعمالك السابقة نفذتها لا أكثر. أما عن فرص العمل المتوفرة في الشركات فهي تختلف باختلاف البلدان والشركات التي تريد العمل فيها، ولكنها تنضوي جميعًا تحت قسم التخصصات الذي تحدثنا فيه بصورة موسعة عن تخصصات علوم الحاسوب التي يمكن للمرء الاختصاص فيها، فمثلًا يمكنك العمل بتخصص مهندس برمجيات أو يتخصص في مجال الذكاء الاصطناعي أو مطور أنظمة تشغيل …إلخ على حسب المسميات الوظيفية المتوفرة في الشركات. انظر مثلًا إلى موقع بعيد، حيث تجد فيه طلبات توظيف من شركات مختلفة حول العالم العربي، وستجد أن معظم الوظائف لا تشترط أي نوع من أنواع الشهادات، بل تشترط معرض أعمال وخبرة سابقة فقط. وتكون رواتب المتخصصين في علوم الحاسوب متعلقة بعدة عوامل منها التخصص والخبرة والأعمال المنجزة وكذلك باختلاف الشركات والأماكن والدول، لكن يمكننا القول بصورة عامة أن رواتبهم أعلى من المهندسين الآخرين، ويمكنك البحث عن المواقع التي تَعرِض لك متوسط الرواتب التي يتلقاها الموظفون حسب المهنة في بلدك ثم البحث فيها عن التخصصات السابقة لرؤية مُعدّل الرواتب في بلدك. ما الفرق بين علوم الحاسب وهندسة الحاسب؟ على عكس الشائع فلا يوجد فرق جوهري بين علوم الحاسب computer science وهندسة الحاسب computer engineering وذلك لأن الجامعات الموجودة في دول مختلفة حول العالم تتعامل مع المصطلحين بطريقة مختلفة، فتجد في بعض الجامعات وفي بعض البلدان أن التخصصين مجرد اسمين مختلفين لنفس المواد الجامعية، فمثلًا الجامعة التي تخرجت منها كان تخصصي فيها هو هندسة وعلوم الحاسوب، أي أنني درست الاثنين معًا، ولم يكن هناك فرق في المواد التي درسناها مع الجامعات الأخرى. أما في بعض البلدان الأخرى مثل الولايات المتحدة هناك فرق حيث تكون علوم الحاسب علومًا أقرب للأقسام النظرية والفهم العام لمختلف المجالات والتخصصات، بينما تركز هندسة الحاسوب بالتحديد على علوم البرمجيات Software وعلوم العتاد Hardware والعلاقات والمشاريع التي يمكن تنفيذها بالدمج بينهما، أي أن هندسة الحاسوب تخصص هندسي تقليدي أقرب للعلوم العملية من تخصص علوم الحاسوب الأقرب للعلوم النظرية المفاهيمية. ويُعَد مصطلح علوم الحاسوب مصطلحًا جديدًا نسبيًا إذ كان التركيز قديمًا على هندسة البرمجيات Software Engineering وهندسة الحاسوب Computer Engineering وشاع المصطلح بعد 2010م وصار يشمل كل هذه العلوم وأكثر. من أين أبدأ بتعلم علوم الحاسوب؟ إن كنت تفكر بالدراسة الجامعية الأكاديمية، فهي من سيرشدك للبدء ويوفر لك المواد المتسلسلة في سنوات الدراسة ويؤمن لك كل شيء خلال رحلتك في دراسة علوم الحاسب وتخصصاته أما إن كنت ممن يريد دراسة مجال علوم الحاسوب ذاتيًا والتخصص فيه دون اللجوء إلى الجامعة، فأنت في المكان الصحيح الذي سيرشدك إلى بداية الطريق. من الأفضل لك أن تبحث عن بعض الدورات المتكاملة للبدء في تعلم علوم الحاسب بدلًا من الدروس المتفرقة، فمن الصعب على طالب جديد لا يعرف شيئًا في المجال أن يدخل فيه مباشرةً ويتعلم ما يحتاج إليه من دروس في كل مادة وتخصص من تلك المواد والتخصصات، بينما الدورات المتكاملة تكون مجهزة وكاملة وفق خطة معينة من متخصصين. عملت أكاديمية حسوب على توفير دورة متكاملة عن أساسيات علوم الحاسب هي دورة علوم الحاسوب وهي دورة شاملة مدتها عشرات الساعات حول علوم الحاسوب بدءًا من أبسط الأساسيات وصولًا إلى الخوارزميات وهياكل البيانات والبرمجة وقواعد البيانات وتطوير الويب وإدارة الخوادم، كما أنها تحت التوسيع والتحديث المستمر، ومن أبرز ميزاتها أن هناك من يتابع سَيْرَك ويجيب على أسئلتك على امتداد الدورة وليست فقط مجرد فيديوهات. ستكون مؤهلًا بعد الدورة من التخصص والغوص في إحدى مجالات علوم الحاسب التي تحبها وتريد تعلمها وقد وفرت الأكاديمية بعد دورة علوم الحاسب دورات أخرى إن أحببت الاختصاص في مجال البرمجة وتطوير البرمجيات لإكمال طريقك وستكون جاهزًا لدخول سوق العمل وبدء الكسب مما تعلمت خلال فترة قصيرة لا تتجاوز السنة بناءً على همتك وعزيمتك. وإذا وصلت إلى مرحلة أنت جاهزٌ فيها لتَعلّم البرمجة، فيمكنك قراءة الدليل الشامل لتعلم البرمجة باستخدام المصادر العربية ففيه أبرز المصادر العربية المتوفرة في الشبكة لتعلم البرمجة. كما يمكنك البحث في الشبكة عن سلاسل فيديوهات أو كتب لتُعلّمك تخصص علوم الحاسب بأي لغةٍ تجيدها، وجوجل مليءٌ بالنتائج عن ذلك كما أن أكاديمية حسوب تعمل جاهدًا على توفير مراجع عالية الجودة لتساعدك في ذلك، فتابع دومًا قسم المقالات البرمجية وقسم الكتب البرمجية. خاتمة وصلنا إلى نهاية هذا المقال بعد أن اطلعنا على أبرز علوم الحاسب وماهية التخصصات الموجودة فيه، ولا تنسَ أن هذا المقال مجرد مقدمة وسيكون عليك صَرْف العديد من الساعات لتتعلّم هذا المجال وتغوص فيه بصورة أعمق. وصحيح أن تعلم علوم الحاسوب قد يكون صعبًا في البداية للوافدين الجدد عليه، ولكن النتيجة مثمرةٌ جدًا حيث يمكنك استخدام أحد أبرز العلوم في العصر الحديث وأهمها لأي غرض أو هدف تريده، سواء كان ذاك الهدف شخصيًا أو ماديًا بهدف الكسب والرزق. إن كان لديك أي سؤال أو استفسار، فلا تتردد بطرحه في التعليقات ونسعد بمشاركتنا تجربتك، أرجو لك التوفيق والسداد! اقرأ أيضًا دليلك الشامل لتعلم البرمجة دليل شامل عن تحليل تعقيد الخوارزمية المرجع الشامل إلى تعلم لغة بايثون ما هي فوائد تعلم البرمجة؟ مدخل إلى الذكاء الاصطناعي وتعلم الآلة1 نقطة
-
مرحبا اريد تعلم تقنياة و كيفة تقديم المواقع ومنصاة لشركاة المصممة اصلا و المتاخرة في محركات البحث في الصفحة البحث الاولى وليس تصميم المواقع وشكرا لكم1 نقطة
-
ما يهم في مواصفات الجهاز الذي ستستخدمه للتصميم هو كرت الشاشة والرامات اختر معالج من فئة i5 / i7 من الجيل السابع وما فوق رامات لا تقل عن 8 جيجا كرت شاشة قوي لا يقل عن gtx 1050ti ولزيادة سرعة الرندرة والتعديل ببرامج التصميم أنصح بشراء هارد ssd من نوع nVMe M2 بذاكرة 256 وما فوق وتنصيب كل برامج التصميم عليها ، هذا الأمر وحده سيسرع من معالجة الصور والتصميم بمقدار 6 أضعاف !!1 نقطة
-
يُعرَّف النموذج المحاكي Mockup لموقع الويب، بأنه التمثيل التصميمي والكلي لأداء الموقع ومظهره، وهو من أهم اختصاصات المصمم المحترف. يتكون النموذج المحاكي لصفحة الويب من بعض خصائص الموقع أو جميعها، ولكنه يختلف عن النموذج العملي Prototype الذي يوضح كيفية عمل الموقع وأدائه؛ في حين يصف التصميم المحاكي Mockups شكل الموقع وتصميمه، كما يساعد على فهم الشكل النهائي للموقع والعناصر التفاعلية فيه، ويتطلّب فهم أيّ تفصيل قد يطرأ على تصميم الموقع. لذلك فهو يُساعد مصمم الويب على فهم تصميم الموقع قبل إطلاقه. إليك هذه النصائح التصميمية التي ستساعدك على تصميم النموذج المحاكي المثالي لموقعك. جمع المعلومات من العميل يجب عليك أن تطلب كافة المعلومات المتعلقة بنموذج الموقع من العميل، وأن تكون هذه المعلومات دقيقةً ومُفَصلة، مثل: التفاصيل المتعلقة بمظهر الموقع، والشعار، والألوان، والصور، ونظام التّنقُّل بين الصفحات. كما يجي عليك فهم حجم التفاعلية التي يريدها العميل في موقعه، وسمات الألوان التي يُفضّلها. انتبه إلى مكان الشعار على الصفحة، والصور الأساسية، ومكانها في النموذج المحاكي، وتذكّر أنّ طلبات العميل هي أهم أولويات العمل. حدد الشريحة المستهدفة تأتي عملية تحديد فئة المستخدِمين التي يستهدفها الموقع في المرتبة التالية لعملية جمع المعلومات من العميل. لهذا حاول أن تحدّد الفئة التي ستزور الموقع، وتتفاعل مع تصميمه، وطريقة تفاعلها مع الموقع والأدوات التي ستستخدمها للتّفاعل. فمن المهم معرفتك إذا كان زوار موقعك مستخدِمين يوميين للإنترنت، أم أنهم نادرًا ما يتصفحون الويب، كما يجب أن يناسب التصميم المحاكي جميع شاشات العرض التي قد يستخدمها زوار الموقع سواءً كانت شاشات الحاسوب الشخصي، أو الحاسوب المحمول، أو الهاتف الذّكي. وفي هذه الحالة ركز على استخدام العناصر المرئية عالية الجودة، لضمان تجربة تصفُّح مثالية على مختلف أنواع الشاشات. صمم بإبداع اطّلِع أولًا على النماذج المحاكية لمواقع الشركات لتحصل على رؤية واضحة عن شكل التصميم والجوانب التي يُغطّيها. يُعَدّ إبداعُك في هذه المرحلة مهمًا جدًا، حيث سيساعدك تصفُّح النماذج الجاهزة على استلهام أفكار جديدة ستجعل تصميمك مختلفًا وإبداعيًّا، لهذا احرص على أن يضُم التصميم كل التّفاصيل المطلوبة بأفضل شكل ممكن، مع اختيار الألوان المناسِبة، وطريقة توضُّع العناصر المختلفة. ستبدأ مهمتك بعد الانتهاء من إغناء بصرك بالتصاميم الجاهزة، وهنا يمكنك تحديد الهيكل العام للتصميم المحاكي بالاعتماد على المعلومات التي جمعتها، انطلاقًا من الأشكال الأساسية والنص الذي سيُعرّف النموذج، بعدها حدّد شكل الشعار ومكانه، وانتبه إلى اختيار الألوان التي تناسب ألوان الشعار وتنسجم معه. اختر الخطوط يجب أن يكون الخط المُستَخدَم في الموقع بحجم مناسب ولون متناسق مع ألوان الموقع، كما يجب مراعاة وضوح الخط فيما يخص الخلفية. إذ يجب عليك مثلًا، تجنَّب استخدام لون أصفر للخلفية مع لون أحمر أو برتقالي للخط. تذكّر أن حجم الخط يجب أن يكون مناسبًا، وأنّ لون الخط يجب أن يكون موحّدًا في جميع صفحات الموقع بغضّ النظر عن نوع الخط. إذ عليك تجنب استخدام نوع الخط Times New Roman لكتابة محتوى الصفحة الأولى ثم استخدام خطوط Arial لكتابة محتوى الصفحة التالية مثلًا، كما يجب عليك الانتباه إلى ضرورة انسجام لون الخط ونوعه مع تصميم نموذج الموقع، وأن يكون الخط مناسبًا، ومقروءًا على كافة شاشات العرض. اختر الصور المناسبة (تُعبِّر الصورة عن ألف كلمة) تُلخّص هذه الحكمة أهمية تحديد الصور المناسبة التي يجب عرضها على صفحات الموقع بحيث تُناسب محتواه، وسيُغني نموذجك المحاكي استخدام مقاطع فيديو بطول دقيقة واحدة للمقطع لتعريف المستخدِمين على علامتك التجارية. لذلك عليك جمع كافة المعلومات المتعلقة بذلك من العميل، لأن اختيار الصور المناسبة سيدعم النموذج المحاكي، إذ تُعبّر الصور عن شركتك وتصِف علامتك التجارية، وتشرح الدافع وراء نموذجك. تشذيب التصميم عندما تُنهي العمل على التصميم وتختار العناصر والألوان، ابدأ بمرحلة الاختبار والتحقق من الأخطاء لتشذيب تصميم النموذج. اختبر المؤثرات والحركة على العناصر التفاعلية وحدِّد مظهر العناصر المختلفة والصور، بعد تطبيق الحركة عليها، واستخدِم إبداعك ومخيّلتك لإضافة تفاصيل تصميمية تُنعش الموقع. فمثلًا، يمكنك اختيار صورة معينة ولتكن صورة شعار الموقع، واستخدامها مثل خلفية للصفحة مع زيادة شفافيتها، بحيث تبقى في مرمى نظر المستخدِم طيلة فترة تصفُّحه للموقع. وعليك اختبار الصور والحركة مع عمليات التنقُّل بين الصفحات، حيث يمكنك استخدام قائمة تنقُّل تنسدل عند تمرير مؤشر الفأرة عليها، أو نترك للمستخدِم خيار استعراض قائمة التنقل بين الصفحات. اختر الأدوات المناسبة لتصميم نموذجك يُعَدّ اختيارك لأدوات التصميم أساس الحصول على عمل إبداعي متكامل يُرضي العميل والمستخدِم، ومن أهم أدوات تصميم النماذج المبدئية للمواقع، ما يلي: برنامج الفوتوشوب هو برنامج لتصميم الرسوميات، حيث يعمل على أنظمة الماك والويندوز، ويُمكّنك من إنشاء تصاميم خلابة، أو التعديل على صور موجودة مسبقًا، وهو أحد أقوى برمجيات التعديل على الصور، وتصميم الرسوم التصويرية بأنواعها. توجد عدة إصدارات لبرنامج الفوتوشوب، منها: Photoshop CC، وPhotoshop Elements، وPhotoshop Lightroom. برنامج Moqups هو برنامج يساعدك على العمل مع واجهات تصميم فارغة، وتمثيلات بيانية متنوعة، وعناصر تصميم أخرى، وهو أحد أفضل برمجيات تصميم النماذج المحاكية، إذ يساعدك على التعامل مع المخططات الشبكية، والرسوم البيانية، والخرائط الذهنية، والنماذج المتنوعة. برنامج الإليستريتور هو برنامج يعتمد على التصاميم الشعاعية Vectors، حيث يُستخدم لرسم وتطوير عناصر تفاعلية لواجهات المواقع. يتيح البرنامج إنشاء الرسوم التخطيطية، والأعمال الفنية للحصول على صور وتصاميم عالية الجودة. ويتميز بإمكانياته العالية في الحصول على تصاميم عالية الوضوح، كما يسمح بتصُّور المظهر الحقيقي الذي سيبدو عليه الموقع. برنامج InVision هو برنامج يسمح بمشاركة تصميم الموقع المحاكي مع بقية أعضاء فريق التصميم أثناء العمل، بهدف الحصول على عمل متكامل. تحسين المحتوى البصري تتجلّى أهمية تحسين المحتوى البصري بوجود مكان مناسب، وكافٍ على صفحة الموقع لعرض الصور، والتصاميم المختلفة، حيث تستطيع بواسطة التصميم المحاكي أن تحدّد طول المدونة، وتُنسّق المحتوى البصري وفقًا لذلك، كما يجب أن تحدّد مساحةً معيّنةً لوضع الصور ضمنها، لأن المستخدِم سيأخذ فكرةً عن المحتوى من خلال إلقاء نظرة سريعة على الصور، ومن الشائع أن ينظر المستخدِم إلى الجهة اليُسرى أولًا، لذلك احرص على وضع الروابط المهمة والصور على الجانب الأيسر من الموقع. إزالة العناصر غير المهمة تُعَدّ الواجهة الجميلة والجذّابة الخطوة الأولى لنجاح أيّ تصميم تفاعلي، لذلك انتبه إلى إزالة كافة العناصر غير الضرورية من التصميم المحاكي للموقع لأنها تؤثِِّر سلبًا على أداء الموقع ووضوحه وإمكانية قراءة محتواه. وتذكّر دومًا أنّ التصميم المتماسك والمتناسق يكمُن في تمثيل العناصر للأداء الصحيح للموقع. لذلك حافظ على العناصر الضرورية، وتخلّص من أي عنصر غير مهم. اختبار النموذج المحاكي يجب أن تختبر عدة عوامل في نهاية تصميم النموذج المحاكي للتحقق من عمله. ابدأ أولًا بالتحقق من أداء النموذج على الأجهزة المختلفة بوجود أنواع مختلفة من طرق الاتصال وأنظمة التكوين، إذ يجب أن يعمل الموقع بنجاح على جميع الأجهزة، وتذكّر ضرورة التحقق من قابلية قراءة المحتوى، مع وجوب تحقيق الانسجام بين التّصاميم المختلفة، مع عملها السليم على جميع الأجهزة، واسمح لباقي أعضاء فريقك باختبار النموذج وتجريبه بغرض التقييم. ختامًا، تذكّر أنّ التصميم المحاكي سيُعرّف الغرض من الموقع، كما سيُجسّد المظهر النهائي لصفحات الموقع، حيث تساعدك النصائح السابقة على تطوير التصميم المبدئي للموقع للوصول إلى أفضل تصميم تفاعلي ممكن، وبإمكانك الاعتماد على متصفحي المواقع المحترفين لفهم أهم التغييرات التي قد يحتاجها موقعك أو واجهة المستخدِم فيه ليحصل الزائر في النهاية على تجربة تصفُّح مثالية. ترجمة وبتصرف للمقال 10tips to create perfect website mockups للكاتب Harsh Raval.1 نقطة
-
مرحبا بك خالد شنوي تصدر محركات البحث يعتمد على عدة معايير سوف الخصها لك في هذه الفقرة القصيرة و بشكل بسيط او اذا كنت تملك دومين جديد لا يمكنك منافسة على كلمات التي لديها عدد كبير من البحث و تملك منافسة عالية في جوجل انا انصحك بالعمل على كلمات فيها زيارات قليلة و منافسة قليلة هذا افضل لك ان كنت تملك موقع جديد و يساعدك على تصدر في محرك البحث بعد مرور اسبوعين او اقل. ان جوجل تحتاج الى فترة 3 اشهر من اجل ظهور موقعك في كلمات مفتاحية التي تريد منافسة عليها . جوجل تعمتد على اثوريتي او مصداقية خاصة للمواقع وهذه يمكنك اكتسابها عبر انشاء محتوي طويل ومفيد للزائر بشكل وليس محشو بكلمات مفتاحية فقط لكي يضل الزائر اطوال مدة في موقعك وربما يشارك مقال الخاص بك في مواقع تواصل الاجتماعي. يمكنك كسب هذه المصداقية ايضا عبر حصول على باك لينك من مواقع ذات مصداقية عالية مثل ويكيبيديا و المواقع الحكومية و المواقع تعليمية التي تختص في نيش الخاص بك. اما بخصوص سيو داخل الموقع on page seo يجب ان يحتوي موقعك وصف قصير و دقيق تضع فيه كلمات المفتاحية خاصة بك. وميتا كيوورد اصبح غير مدعومة من جوجل و ان وضعت كييوورد لا تضع اكثر من 13 كلمة مفتاحية.1 نقطة
-
شكرا لك جزيلا سيد wael aljamal على الجواب السريع لاكن كان سؤالي ليس تصميم موقع ب seo بل عند بحث في محركات البحث مثلا كلمة (رياضة ) نتائج البحث تعطيك 10 صفحات بمافيهم الصفحة الاولى كيف العمل على جلب الواقع المتاخرة في الصفحة 10و وضعهم في الصفحة الاولى بالخبراة او بالبرامج او مواقع واخد عمولة على عملي هدا وشكر لك1 نقطة
-
تحسين محركات البحث هو مجال شاسع و من المجحف تلخيصه في مقال واحد , وتنقسم الإستراتيجيات إلى قسمين: على الصفحة، وخارج الصفحة on-page & Off-Page . نذكر من بينها : على الصفحة: شاملة كل العمليات التي يتم القيام بها داخل الموقع نفسه , من تعديل أكواد و إلتزام بنمط معين من كتابة الكود و غيرها . أمثلة : إضافة كلمات مفتاحية و وصف الموقع و أكواد الميتا تاج meta tags ومتابعتها . إضافة عناوين للوسوم الخاصة بالصور داخل الموقع . (alt) . إضافة صفحة robots.txt التي من شأنها توجيه محركات البحث إلى الصفحات المطلوب أرشفتها و تلك الأخرى الممنوعة أو حتى حظر بعد الصفحات . إضافة صفحة خريطة الموقع sitemap.xml و تحديثها بشكل مستمر إن كان الموقع ديناميكي غير ثابت . سكربتات و أكواد الأنماط و النماذج schemas.org لصفحات الموقع لإظهارها بشكل أكثر إحترافية (يمكنك التفصل في الموضوع أكثر من هنا ). محتوى الصفحة , لا بد أن يكون فريدا و غير مأخوذ من أية مواقع أخرى . إضافة الموقع إلى Google Search Console و متابعة الموقع . تقسيم المحتوى إلى عناوين فرعية (headings). خارج الصفحة: شاملة كل العمليات التي تقوم بها خارج الموقع لكسب ثقة محركات البحث، مثل الروابط الخارجية و الـ back linking (ينبغي القراءة في هذا الموضوع بشكل أكثر حيث أن محركات البحث و خصوصا غوغل تعتبر العملية غير شرعية ولو تم كشف ذلك سيتم حظر الموقع نهائيا من الأرشفة , و ذلك لأن منطق محركات البحث هو في أن شيوع موقع يعني تناقل رابطه بين المواقع و هو أمر يجب أن يقوم به زوار و رواد الموقع لا ملاكه أو أصحابه ) . كما يمكنك الإستعانة بهاته المنصات لتحسين ظهور مواقعك و منصاتك : إضافة غوغل كروم lighthouse . Google Search Console . تحليلات غوغل . منصة و أدوات SEMrush لتحسين محركات البحث . أدوات Screaming Frog طبعا تحسين ظهور موقعك لن يكون آنيا و إنما هي عملية تحتاج صبر , وقت و متابعة دائمة .1 نقطة
-
من أجل أن يتحسن ترتيب موقعك في الصفحات الاولى بمنصات البحث "غوغل، ياهو، بينغ" لا بد وأن تستخدم SEO بطريقة صحيحة. SEO: يعني ال search engine optimization بمعنى أن تقوم بالعمل على المحتوى المتواجد داخل الموقع الخاص بك بطريقة ذكية تتماشى مع الخواريزميات الخاصة بمحركات البحث. يجب تكرار الكلمات المفتاحية في جسم المقالة و كتابة عنوانها بطريقة مناسبة (كما يمكن أن يبحث عنها شخص آخر لتحقيق أكبر عدد تطابق في الكمات) يمكنك اتباع بعض الخطوات لتحسين SEO: فمن أجل أن تحقق تحسن في ملائمته لمحركات البحث، عليكِ أن: كتابة محتوى ملائم لمجال موقعك بصورة مستمرة. العمل على تحسين ال structure الداخلي الخاص بمحتوى موقعك. الاهتمام بالروابط الداخلية للموقع. حل المشاكل التقنية بالنسبة للموقع. كما يمكن استخدمات الخدمات المدفوعة و الإعلانات. يمكن توظيف مختص من موقع مستقل أو خمسات لإنجاز المهمة.1 نقطة
-
ينبغي تصريف البرامج المكتوبة بلغة C++ قبل أن تتمكن تلك البرامج من العمل، وستجد مجموعة كبيرة ومتنوّعة من برامج التصريف أو المصرِّفات (compilers) المتاحة والمناسبة لنظام التشغيل الذي تعمل به. التصريف بواسطة GCC التصريف دون تحسين مفيد في المراحل الأولى من التطوير والتنقيح، على الرغم من أنّ خيار -Og موصىً به في الإصدارات الحديثة من GCC، وبناء عليه فيمكن تصريف وربط ملف تنفيذي وغير محسَّن على افتراض أن هناك ملفًا مصدريًا واحدًا يسمى main.cpp، وذلك على النحو التالي: g++ - o app - Wall main.cpp - O0 ولإنتاج ملف مُحسّن قابل للتنفيذ لاستخدامه في الإنتاج، استخدم أحد خيارات -O (راجع: ،-O1، -O2، -O3، -Os، -Ofast): g++ - o app - Wall - O2 main.cpp في حال حذف الخيار -O، سيُستخدَم -O0 الذي يعني إلغاء التحسينات، كخيار افتراضي، واعلم أن تحديد -O بدون رقم يجعله مساويًا لـ -O1. وكخيار بديل، يمكن أن نستخدم رايات التحسين (optimization flags) من مجموعات O -أو تحسينات تجريبية أخرى- مباشرةً. انظر المثال التالي حيث يبني البرنامج مع التحسين -O2، بالإضافة إلى راية واحدة من مستوى التحسين -O3: g++ -o app -Wall -O2 -ftree-partial-pre main.cpp لإنتاج ملفّ تنفيذي مُحسّن لأجل منصة معيّنة -لاستخدامه على جهاز له نفس المعمارية-، استخدم: g++ -o app -Wall -O2 -march=native main.cpp سوف ينتُج عن الشيفرَتين أعلاه ملفٌّ ثنائي (binary file) يمكن تشغيله باستخدام .\app.exe على Windows، أو باستخدام ./app على Linux و Mac OS، كما يمكن أيضًا تخطي الراية -o، وفي هذه الحالة سينشئ GCC الملفّ التنفيذي الافتراضي a.exe على Windows و a.out على الأنظمة الشبيهة بيونكس (Unix-like). لأجل تصريف ملف دون ربطه، استخدم الخيار -c: g++ -o file.o -Wall -c file.cpp سينتج عن هذا ملفٌّ يحمل الاسم file.o، والذي يمكن ربطه لاحقًا بملفّات أخرى لإنتاج ملف ثنائي: g++ -o app file.o otherfile.o انظر gcc.gnu.org للمزيد من التفاصيل حول خيارات التحسين، خاصة -Og، وهو التحسين مع التركيز على تجربة التنقيح (debugging)، الذي يوصى باستخدامه في دورات تحرير-تصريف-تنقيح القياسية - standard edit-compile-debug cycle)، وكذلك -Ofast الذي يشمل جميع التحسينات، بما في ذلك تلك التي تتجاهل الامتثال الصارم للمعايير. تُمكِّنك راية -Wall من إطلاق تحذيرات من بعض الأخطاء الشائعة، وينبغي أن تُستخدم دائما، ويوصى باستخدام -Wextra لتحسين جودة الشيفرة، وكذلك رايات التحذير الأخرى التي لا تُمكَّن تلقائيًا من قِبل -Wall و -Wextra. إذا كانت الشيفرة تتوقع معيارًا محدّدًا للغة C++، فيمكنك تحديد المعيار الذي تريد استخدامه عن طريق تضمين راية -std=. وتتوافق القيم المدعومة مع سنة الإصدار النهائي لمعيار ISO C++، واعتبارا من GCC 6.1.0 فإنّ القيم الصالحة لـ std= هي: c++98 / c++03، c++11، c++14، وc++17 / c++1z. لاحظ أن القيم المفصولة بشرطةٍ مائلة / متكافئة. g++ -std=c++11 <file> يتضمّن GCC بعض الإضافات (Extensions) (extensions) الخاصة بالمُصرِّف، والتي تُعطَّل عندما تتعارض مع معيار قياسي محدّد من قبل راية -std=، فإذا أردت التصريف مع كل الإضافات (Extensions) المُمَكَّنة، فاستخدام القيمة gnu++XX، حيث تمثّل XX أيًّا من السنوات المذكورة أعلاه. ويُستخدَم المعيار الافتراضي في حالة عدم تعريف أيّ منها، وبالنسبة لإصدارات GCC التي تسبق 6.1.0 فإنّ الإعداد الافتراضي هو -std = gnu++03، أما في GCC 6.1.0 والإصدارات الأحدث فإن الإعداد الافتراضي هو -std=gnu++14. لاحظ أنّه نظرا لوجود بعض الأخطاء (bugs) في GCC، فيجب أن تكون راية pthread- حاضرة في التصريف والربط إن أردت أن يدعم GCC معيار الخيوط (threading) الذي أُدخل في C++11، مثل std::thread و std::wait_for. قد لا ينتج عن حذفها عند استخدام الخيوط أيّ تحذيرات، ولكن قد تحصل على نتائج غير صحيحة في بعض المنصّات. الربط بالمكتبات Linking with libraries استخدم خيار -l لتمرير اسم المكتبة: g++ main.cpp -lpcre2-8 # 8bit code units (UTF-8) في وحدات الشيفرات ثمانية البتّات PCRE2 هي مكتبة pcre2-8 إذا لم تكن المكتبة موجودة في مسار المكتبة القياسية، فأضف المسار باستخدام -L: g++ main.cpp -L/my/custom/path/ -lmylib يمكن ربط عدّة مكتبات معًا: g++ main.cpp -lmylib1 -lmylib2 -lmylib3 إذا كانت إحدى المكتبات تعتمد على مكتبة أخرى، فضع المكتبة المعتِمدة قبل المكتبة المستقلة: g++ main.cpp -lchild-lib -lbase-lib أو اترك الرابط (linker) يتكفّل بتحديد الترتيب عبر الخيارات --start-group و --end-group (ملاحظة هذا له تكلفة كبيرة على الأداء): g++ main.cpp -Wl,--start-group -lbase-lib -lchild-lib -Wl,--end-group التصريف باستخدام Visual Studio (واجهة رسومية) نزِّل Visual Studio Community 2015 وثبِّته. افتح Visual Studio Community . انقر على File، ثم NewK ثم Project. انقر على Templates ثم ++Visual C ثم Win32 Console Application، ثم ضع اسمًا للمشروع وليكن MyFirstProgram. انقر على Ok. انقر على "Next " في النافذة التالية. اختر الخانة Empty project تحت الخيارات الإضافية (Additional options) ثم انقر على "Finish": انقر بالزر الأيمن فوق مجلد ملف المصدر ثم Add، ثم New Item: حدّد ملف C++ وقم بتسمية main.cpp، ثم انقر فوق Add: انسخ والصق الشيفرة التالية في الملف الجديد main.cpp: #include <iostream> int main() { std::cout << "Hello World!\n"; return 0; } ينبغي أن تبدو بيئة العمل لديك كما يلي: انقر على Debug ثم Start Without Debugging، (أو اضغط على ctrl + F5): يجب أن تحصل على الخرج التالي في سطر الأوامر: مُصرِّفات الشبكة Online Compilers توفّر العديد من المواقع مصرّفات C++ يمكن الوصول إليها عبر شبكة الإنترنت، وتختلف مزايا وإمكانيات مصرّفات الشبكة من موقع إلى آخر، ولكنها عادة ما تسمح بالقيام بما يلي: لصق الشيفرة في نموذج في المتصفّح. تحديد بعض خيارات المصرّف، وتصريف الشيفرة. الحصول على خرج المصرّف و/أو البرنامج. عادةً ما تكون المُصرِّفات الشبكية مقيّدة إذ أنّها تتيح لأيّ شخص تشغيل المصرّف وتنفيذ شيفرات عشوائية على الخادم، وهذا قد يكون مصدر خطر في حال تنفيذ شيفرات ضارة. قد تكون مصرّفات الشبكة مفيدة للأغراض التالية: تشغيل مقتطف صغير من شيفرة جهاز يفتقر إلى مصرّف C++ (مثل الهواتف الذكية والأجهزة اللوحية وما إلى ذلك). التحقّق من أنّ الشيفرة تُصرَّف بنجاح في مختلف المُصرِّفات، وتعمل بنفس الطريقة بغض النظر عن المُصرِّف الذي صُرِّفت فيه. تعلُّم أو تعليم أساسيات C++. تعلّم ميزات C++ الحديثة (C++ 14 و C++ 17 في المستقبل القريب) إذا لم يكن لديك مصرّف C++ حديث على جهازك. رصد الأخطاء التي يمكن أن تكون موجودة في المصرّف الذي تعمل به بمقارنته بمجموعة كبيرة من المصرّفات الأخرى. التحقق ممّا إذا كانت الإصدارات اللاحقة من المصرّف الذي تعمل قد صُحِّحت في حال لم تتوفر تلك الإصدارات على جهازك. حل المشاكل والتمارين عبر الشبكة. بالمقابل، لا ينبغي استخدام مُصرِّفات الشبكة لأجل: تطوير برامج كاملة (ولو كانت صغيرة) باستخدام C++، وفي العادةً فإن مصرّفات الشبكة لا تسمح بالارتباط مع مكتبات خارجية، أو تنزيل أدوات البناء. إجراء حسابات مكثفة. موارد الخادم الحسابية محدودة، لذلك سيوقَف أيّ برنامج يقدّمه المستخدم بعد بضع ثوانٍ من بدء تنفيذه، فوقت التنفيذ المسموح به يكفي عادة للاختبار والتعلم فقط. قرصنة ومهاجمة الخادم الذي يعمل عليه المُصرِّف، أو أيّ جهة خارجية مُستضافة على الشبكة. أمثلة على المصرفات الشبكية: codepad.org: مُصرِّف مع إمكانية مشاركة الشيفرة، وتحرير الشيفرة بعد التصريف، لكن التحذيرات و الأخطاء لا تعمل بشكل جيد. coliru.stacked-crooked.com: مصرّف يمكّنك من أن تحدّد سطر الأوامر، ويتيح لك اختيار أحد المُصرِّفين GCC أو Clang. cpp.sh: مُصرِّف يدعم C++ 14، لا يسمح لك بتحرير سطر أوامر المصرّف، ولكن يوفّر بعض الخيارات عبر عناصر التحكم في واجهة المستخدم الرسومية. gcc.godbolt.org: يوفر قائمة واسعة من إصدارات المُصرِّف، والمعماريات، وهو مفيد للغاية لمن يحتاج إلى التحقّق من عمل الشيفرة في عدّة مُصرِّفات مختلفة. والمصرفات التالية هي بعض المصرّفات المتاحة: GCC و Clang و MSVC ومصرّف Intel و ELLCC و Zapcc، مع توفّر واحد أو أكثر من هذه المصرّفات للمعماريّات ARM و ARMv8 (مثلARM64) و Atmel AVR و MIPS و MIPS64 و MSP430 و PowerPC و x86 و x64. أيضًا، يمكن تعديل وسائط سطر الأوامر الخاص بالمُصرِّف. ideone.com: يُستخدم على نطاق واسع لتوضيح سلوك الشيفرة، ويوفر كلًّا من GCC و Clang، لكنّه لا يسمح بتحرير سطر أوامر المصرّف. melpon.org/wandbox: يدعم العديد من إصدارات Clang و GNU / GCC. onlinegdb.com: بيئة تطوير متكاملة لكن محدودة، تتضمّن محررًا ومُصرِّفًا (gcc) ومنقّحًا (gdb). rextester.com: يوفر المُصرِّفات Clang و GCC و Visual Studio لكل من C و C++ (إضافة إلى مُصرِّفات خاصّة بلغات أخرى)، ومكتبة Boost. tutorialspoint.com/compile_cpp11_online.php: صدفة UNIX متكاملة، مع مصرّف GCC، ومستكشف مشاريع سهل الاستخدام. webcompiler.cloudapp.net: مُصرِّف لبرنامج Visual Studio 2015، مُقدّم من قبل Microsoft كجزء من RiSE4fun. التصريف باستخدام Visual C++ (سطر الأوامر) بالنسبة للمبرمجين الذين اعتادوا على العمل بالمصرّفَين GCC أو Clang، والذين يودّون الانتقال إلى Visual Studio، أو المبرمجين الذين يفضّلون العمل بسطر الأوامر بشكل عام، يمكن استخدام مصرّف Visual C++ من سطر الأوامر إلى جانب بيئة تطوير متكاملة (IDE). وإذا كنت ترغب في تصريف شيفرتك من سطر الأوامر في Visual Studio، فسيكون عليك إعداد بيئة سطر الأوامر، عن طريق فتح: Visual Studio Command Prompt/Developer Command Prompt/x86 Native Tools Command Prompt/x64 Native Tools Command Prompt أو شيئٍا من هذا القبيل (يختلف الأمر حسب إصدار Visual Studio الذي تعمل به)، أو في موجّه الأوامر، من خلال الانتقال إلى المجلّد الفرعي VC في مجلّد التثبيت الخاصّ بالمصرّف (يكون عادةً \Program Files (x86)\Microsoft Visual Studio x\VC، حيث يمثّل x رقم الإصدار (مثل 10.0 لعام 2010، أو 14.0 لعام 2015)، ثمّ تنفيذ الملفّ VCVARSALL مع مُعامل سطر الأوامر المُحدّد هنا. لاحظ أنّه على عكس GCC، فإنّ Visual Studio لا يوفّر واجهة للرابط (link.exe) عبر المصرّف (cl.exe)، ولكنّها توفّر الرابط (linker) كبرنامج منفصل، ويستدعيه االمُصرِّف عند الإنهاء. ويمكن استخدام cl.exe و link.exe بشكل منفصل مع عدّة ملفّات وخيارات، أو إخبار cl بتمرير الملفّات والخيارات إلى link إذا كانت المهمّتان ستُنجزان معًا، وستُترجم خيارات الربط المُحدّدة لـ cl إلى خيارات خاصة بـ link، وتُمرّر الملفات غير المُعالجة بواسطة cl مباشرة إلى الرابط link. انظر هذه الصفحة للمزيد عن وسائط link. لاحظ أنّ الوسائط التي تخصّ cl حسّاسة لحالة الأحرف، وذلك على خلاف وسائط link، وأن بعض الأمثلة التالية تستخدم المتغيّر "current directory" الخاصّ بصدفة (shell) ويندوز، %cd%، عند تحديد أسماء المسار المطلق (absolute path names)، ويُوسَّع هذا المتغيّر إلى مجلّد العمل الحالي (current working directory). وفي سطر الأوامر، سيكون هو المجلّد الذي كنت تستخدمه عند تشغيل cl، وهو محدَّد في موجّه الأوامر - command prompt - افتراضيًا (مثلًا، في موجّه الأوامر C:\src>، فإنّ %cd% سيكون C:\src\). يمكنك تصريف وربط ملف تنفيذي غير مُحسَّن (unoptimised executable) بافتراض أنّ هناك ملفّا مصدريًا واحدًا يُسمّى main.cpp في المجلد الحالي، وهذا مفيد في مرحلة التطوير الأولي والتنقيح، عبر استخدام أيٍّ ممّا يلي: cl main.cpp // "main.obj" إنشاء ملف // "main.obj" إجراء الربط مع // "main.exe" إنشاء الملف القابل للتنفيذ cl /Od main.cpp في المثال السابق، سيتصرف cl /Od main.cpp كسابقه، لكن يكون "Od/" هو خيار "Optimisation: disabled"، ويكون الخيار الافتراضي عند عدم تحديد أي من خيارات "O/". بافتراض أنّ هناك ملفًّا مصدريًا إضافيًا " niam.cpp " في نفس المجلّد، فستنشئ الشيفرة التالية الملفين "main.obj" و "niam.obj"، ثم يجري الربط معهما، ومن ثم ينشئ الملف التنفيذي "main.exe": cl main.cpp niam.cpp يمكنك أيضًا استخدام محارف البدل (wildcards)، ستنشئ الشيفرة التالية ملف كائن "main.obj" إضافة إلى ملف كائن لكل ملف cpp. في المجلد التالي: "%cd%\src" ثم تُجري الربط مع "main.obj" وكل ملف كائن مُنشأ، وستكون كل ملفات الكائن في المجلد الحالي، ثم تولِّد ملف main.exe. cl main.cpp src\* .cpp لإعادة تسمية الملف القابل للتنفيذ أو نقله، استخدم أحد الخيارات التالية: cl /o name main.cpp // "name.exe" تولد ملف قابل للتنفيذ cl /o folder\ main.cpp // "%cd%\folder" في المجلد "main.exe" إنشاء ملف cl /o folder\name main.cpp // "%cd%\folder" في المجلد "main.exe" إنشاء ملف cl /Fename main.cpp // "/o name" مثل cl /Fename main.cpp // "/o folder\" مثل cl /Fefolder\name main.cpp // "/o folder\name" مثل يمرِّر كلٌّ من /o و /Fe معاملاتِهما (دعنا نسميها o-param) إلى link على شكل /OUT:o-param، مع إلحاق الامتداد المناسب (بشكل عام .exe أو .dll) لتسمية o-param بما يُناسب، في حين أنّ لـ /o و /Fe نفس الوظيفية - على حدّ علمي - إلّا أنّ الأخير هو المفضل في Visual Studio. لقد أصبحت /o مهمَلة، ويبدو أنّها تًقدّم بشكل أساسي للمبرمجين الذين اعتادوا العمل بالمصرّفَين GCC و Clang. لاحظ أنّه على الرغم من أنّ المسافة الفارغة بين /o واسم المجلد المحدّد اختيارية، إلا أنه لا يمكن وضع مسافة بيضاء بين /Fe واسم المجلد المحدّد. بالمثل، يمكن إنشاء ملف تنفيذي مُحسّن (لاستخدامه في مرحلة الإنتاج) على النحو التالي: سنحسِّن حجم الملف التنفيذي في السطر الأول لكن قد يكون هذا على حساب سرعة التنفيذ، أما في السطر الثاني فيحسِّن سرعة البرنامج، لكن ربما يجعل هذا حجم الملف أكبر. cl /O1 main.cpp cl /O2 main.cpp هنا ننشئ ملفات خاصة لتحسين البرنامج، مما يسمح لسطر الأوامر أن يأخذ كل وحدات الترجمة في حسبانه خلال عملية التحسين، وسيمرر خيار توليد شيفرة الرابط-الوقت (Link-Time Code Generation) أو LTCG/، إلى الرابط LINK، ليخبره باستدعاء سطر الأوامر خلال مرحلة الربط لإجراء تحسينات إضافية. وينبغي ربط كائن الملف المُنشأ مع LTCG/ في حال عدم إجراء الربط في ذلك الوقت، كما يمكن استخدامها مع خيارات التحسين الأخرى في سطر الأوامر، … cl /GL main.cpp other.cpp أخيرًا، لإنتاج ملف تنفيذي مُحسَّن لأجل منصّة معيّنة (لاستخدامه في الإنتاج على جهاز ذي معمارية معيّنة)، فعليك اختيار موجّه الأوامر المناسب، أو مُعامل VCVARSALL المناسب للمنصّة المُستهدفة. ويجب أن يرصُد الرابط (link) المنصّة المطلوبة من ملفّات الكائن؛ وإلّا، فعليك استخدام الخيار /MACHINE لتعريف المنصّة المُستهدفة بشكل صريح. مثال: إذا أردت التصريف للتنفيذ على معمارية 64 بت، وتعذر على الرابط LINK أي يرى المنصة المستهدفة: cl main.cpp / link / machine: X64 سينتج أيًا مما سبق ملفًا تنفيذيًا يحمل الاسم المحدّد من قِبل /o أو /Fe، وفي حالة عدم توفير أيّ منهما، سيحمل اسمًا مطابقًا للملف المصدري الأول أو ملفِّ الكائن المحدّد للمُصرِّف. cl a.cpp b.cpp c.cpp // "a.exe" إنشاء cl d.obj a.cpp q.cpp // "d.exe" إنشاء cl y.lib n.cpp o.obj // "n.exe" إنشاء cl / o yo zp.obj pz.cpp // "yo.exe" إنشاء لتصريف الملفّات دون ربط: cl / c main.cpp // "main.obj" إنشاء ملف تخبر الشيفرةُ السابقة سطرَ الأوامر cl بالخروج دون استدعاء link، وتنتج ملفّ كائن يمكن ربطه لاحقًا بالملفّات الأخرى لإنتاج ملف ثنائي. في الشيفرة التالية، ينشئ السطر الأول ملف الكائن niam.obj، ويجري الربط مع niam.obj و main.obj، ثم ينشئ الملف التنفيذي main.exe. ويجري السطر الثاني ربطًا مع niam.obj و main.obj، ثم ينشئ الملف التنفيذي main.exe. cl main.obj niam.cpp link main.obj niam.obj هناك مُعاملات أخرى مهمّة لسطر الأوامر، ومن المفيد جدًا معرفتها، منها ما يلي: cl /EHsc main.cpp: يشير EHsc إلى أنه لن تُمسك إلا اعتراضات ++C القياسية، وأن دوال C الخارجية لن ترفع اعتراضات (exceptions). يوصى بهذا المعامل لمن يريد كتابة شيفرة محمولة ومتعددة المنصات. cl /clr main.cpp: يشير clr/ إلى أن الشيفرة يجب أن تُصرَّف لاستخدام اللغة المشتركة لوقت التشغيل، وهي آلة وهمية خاصة بإطار عمل NET.، وتسمح باستخدام لغة C++/CLI الخاصة بميكروسوفت إضافة إلى سطر أوامر ++C، وتنشئ ملفًا تنفيذيًا يتطلب NET. ليعمل. cl /Za main.cpp: يشير Za/ إلى وجوب تعطيل إضافات (Extensions) ميكروسوفت، وأن الشيفرة يجب أن تُصرَّف وفق مواصفات ISO للغة ++C حصرًا، وهذا ضروري لضمان محمولية البرنامج. cl /Zi main.cpp: يولد Zi/ ملف قاعدة بيانات PDB للبرنامج من أجل استخدامه عند تنقيح برنامج ما دون التأثير على مواصفات التحسين، ويُمرَّر خيار DEBUG/ إلى الرابط LINK. cl /LD main.cpp: يخبر LD/ سطرَ الأوامر أن يضبط LINK كي يولد ملف DLL بدلًا من ملف تنفيذي، وسينتج الرابط ملف DLL إضافة إلى ملفي EXP و LIB لاستخدامهما عند الربط. مرر ملف LIB المرتبط بملف DLL إلى سطر الأوامر أو الرابط عند تصريف تلك البرامج، وذلك لاستخدام الأخير في برامج أخرى. cl main.cpp /link /LINKER_OPTION: يمرر link/ كل ما بعده إلى الرابط مباشرة دون تحليل. استبدل LINKER_OPTION/ بخيارات الرابط التي تريد. بالنّسبة للذين لديهم خبرة في التعامل مع اليونكسات وأشباهها، و/أو GCC / Clang و cl و link، وأدوات سطر أوامر Visual Studio الأخرى، يمكنهم أن يقبلوا المُعاملات المُحدّدة باستخدام الواصلة - (مثل -c) بدلًا من الشرطة المائلة (مثل /c). إضافة إلى ذلك، يتعرّف نظام ويندوز على الشرطة المائلة العكسية \ (backslash) أو الأمامية / (slash) ويعدّهما فواصل صالحة للمسارات، لذلك يمكن استخدام المسارات التي على نمط يونكس وما شابهه. يسهّل هذا تحويل أسطر أوامر المصرّف من g++ أو clang++ إلى cl، أو العكس، بالحد الأدنى من التغييرات. g++ - o app src / main.cpp cl - o app src / main.cpp ستحتاج إلى البحث عن أوامر مكافئة في توثيق المصرّف الذي تستخدمه و/أو في مواقع توثيق أخرى، وذلك عند محاولة نقل سطور الأوامر التي تستخدم خيارات معقّدة لـ g++ أو clang++. وإن احتجت إلى استخدام ميزات لغة معيّنة في شيفرتك، فيلزم استخدام إصدار محدّد من MSVC. ومن الممكن في Visual C++ 2015 التحديث 3 اختيار إصدار المعيار الذي تريد اعتماده عند التصريف عبر الراية /std، والقيمُ الممكنةُ هي /std:c++14 و /std:c++latest (ستتبعُهما /std:c++17 قريبًا). ملاحظة في الإصدارات القديمة من هذا المُصرِّف، كانت بعض رايات المزايا متوفرة، بيْد أنّها كانت في الغالب تُستخدم لمعاينة الميزات الجديدة. التصريف باستخدام Clang بما أن الواجهة الأمامية لمصرّف Clang مُصمّمة لتكون متوافقة مع GCC، فإنّ معظم البرامج التي يمكن تصريفها عبر GCC ستُصرَّف كذلك عند استبدال clang++ بـg++ في برامج البناء النصية (build scripts)، وفي حال عدم إعطاء -std=version، فسيُستخدَم الإصدار gnu11. يمكن لمستخدمي ويندوز الذين اعتادوا على MSVC استبدال clang-cl.exe بـ cl.exe. واعلم أن clang يحاول بشكل افتراضي أن يكون متوافقًا مع أحدث إصدار مُثبّت من MSVC. ويمكن استخدام clang-cl عبر تغيير Platform toolset في خاصّيات المشروع، وذلك عند التصريف باستخدام visual studio. وفي كلتا الحالتين، سيكون clang متوافقًا عبر واجهته الأمامية وحسب، لكنه سيحاول أيضًا بناء ملفات ثنائية متوافقة. كذلك يجب على مستخدمي clang-cl أن ينتبهوا إلى أنّ التوافق مع MSVC ليس كاملًا. لاستخدام clang أو clang-cl، يمكن استخدام التثبيت الافتراضي على توزيعات محدّدة من Linux، أو تلك المُجمَّعة في بيئات التطوير المتكاملة - IDEs - (مثل XCode على Mac). أما بالنسبة للإصدارات الأخرى من هذا المُصرِّف، أو في المنصّات التي لم يُثبّت عليها، فيمكن تنزيله من صفحة التنزيل الرسمية. إذا كنت تستخدم CMake لبناء شيفرتك، فيمكنك تبديل االمُصرِّف عادة عن طريق تعيين متغيّرَي البيئة CC و CXX على النحو التالي: mkdir build cd build CC=clang CXX=clang++ cmake .. cmake --build . عملية التصريف في C++ بعد تطوير برنامج بلغة C++، فإنّ الخطوة التالية هي تصريف ذلك البرنامج قبل تشغيله. والتصريف (compiling) هو العملية التي تحوّل البرنامج المكتوب بلغة قابلة للقراءة البشرية، مثل C و C++ وغيرهما، إلى شيفرة الآلة، وهي شيفرة يمكن أن تُفهمها وحدة المعالجة المركزية مباشرة. على سبيل المثال، إذا كان لديك ملفّ مصدَري مكتوب بلغة C++ باسم prog.cpp، وقمت بتنفيذ الأمر compile … g++ -Wall -ansi -o prog prog.cpp ستحدث 4 مراحل رئيسية عند إنشاء ملف تنفيذي من الملف المصدري. يأخذ معالج C++ الأولي ملفّ الشيفرة المصدرية ويتعامل مع الترويسات (include#)، ووحدات الماكرو (#define) وموجّهات المعالج الأخرى. تُصرّف شيفرة C++ المصدرية المُوسّعة التي تم إنتاجها من قبل معالج C++ الأوّلي إلى لغة التجميع (assembly) المناسبة للمنصّة. تُصرَّف الشيفرة المجمّعة التي تم إنتاجها بواسطة المصرّف إلى كائن شيفرة (object code) مناسب للمنصة. يُربط ملفّ كائن الشيفرة المُنتج من قبل المجمّع مع ملفّات كائنات الشيفرة الخاصة بمكتبات الدوال المُستخدمة، وذلك لإنتاج مكتبة أو ملف قابل للتنفيذ. المعالجة الأولية Preprocessing يُعالج المعالج الأولي تعليمات المعالج، مثل #include و #define، وهو غير واعٍ بصيغة C++، لذا يجب توخّي الحذر عند استخدامه. يعمل المعالج الأوّلي على ملفّ ++C مصدري واحد عن طريق استبدال محتوى الملفّات المناسبة (والتي تكون عادة مجرّد تصريحات) بتعليمات #include، ويستبدل وحدات الماكرو (#define)، ويختار عدة أجزاء من النص بحسب تعليمات #if و #ifdef و #fndef. يعمل المعالج الأوّلي على مجموعة من وحدات المعالجة الأوّلية (preprocessing tokens)، ويُعُرِّف استبدال الماكرو على أنّه استبدال مقاطع بمقاطع أخرى (يتيح المُعامل ## دمجَ شيفرَتين إذا كان ذلك مناسبًا). بعد كل هذا، يُنتج المعالج الأوّلي خرجًا واحدًا يمثّل مجرى من المقاطع الناتجة عن التحويلات الموضّحة أعلاه، ويضيف أيضًا بعض العلامات/الوسوم الخاصة التي تخبر المُصرِّف بالسطر الذي جاء منه كل مقطع، حتّى يتمكّن من صياغة رسائل الخطأ المناسبة. قد تنتج بعض الأخطاء في هذه المرحلة عند استخدام التعليمتين #if و #error. ونستطيع إيقاف العملية في مرحلة المعالجة الأولية باستخدام راية المصرّف أدناه: g++ -E prog.cpp التصريف تُجرى خطوة التصريف على كل خرج من مخرجات المعالج الأوّلي (Preprocessor)، ويحلّل االمُصرِّف شيفرة C++ المصدرية الخالصة - دون أي تعليمات للمعالج الأولي الآن- ثمّ يحوّلها إلى شيفرة المجمّع، ثم يستدعي الواجهة الخلفية الأساسية (underlying backend)، وهي المجمّع في سلسلة الأدوات، التي تجمّع تلك الشيفرة وتحوّلها إلى شيفرة الآلة، وتنتج ملفًّا ثنائيًا وفق تنسيق معيّن (مثل ELF أو COFF أو a.out …). تحتوي ملفات الكائنات على الشيفرة المُصرَّفة (في شكل ثنائي) للرموز المُعرَّفة في المدخلات، ويشار إلى الرموز الموجودة في ملف الكائن بالاسم. وتستطيع ملفات الكائنات أن تشير إلى الرموز التي لم تُعرِّف بعد، كأن تستخدم تصريحًا بدون توفير تعريف له، فلا يمانع المُصرِّف ذلك، وسينتج ملفَّ الكائن بلا مشاكل طالما أنّ الشيفرة المصدرية مصاغة بشكل جيّد. تسمح لك المصرّفات عادةً بإيقاف عملية التصريف في هذه المرحلة، وهو أمر مفيد للغاية إذ يُمكِّنك من تصريف كل ملفّ مصدري بشكل منفصل، وعليه فلن تكون بحاجة إلى إعادة تصريف كل شيء إن لم تكن غيرت إلا ملفًّا واحدًا فقط. يمكن وضع ملفات الكائنات الناتجة في أرشيفات خاصة تسمّى المكتبات الساكنة (static libraries)، لتسهيل استخدامها لاحقًا، ويُبلَّغ في هذه المرحلة عن أخطاء المصرّف "المعتادة"، مثل أخطاء الصيغة، أو أخطاء فشل تحليل التحميل الزائد. لإيقاف العملية بعد خطوة التصريف، يمكن استخدام الخيار -S: g++ -Wall -ansi -S prog.cpp التجميع Assembling ينشئ المجمّع شيفرة الكائن، وقد ترى على نظام UNIX ملفّات ذات لاحقة .o (أو .OBJ على MSDOS) للدلالة إلى ملفات كائنات الشيفرَة (object code files). في هذه المرحلة، يحوّل المجمّع تلك الملفّات من شيفرة مُجمَّعة (assembly code) إلى تعليمات على مستوى الآلة، كما سيكون الملفُّ المُنشأ كائنَ شيفرة قابلة للنقل (relocatable object code)، ومن ثم ستنشئ مرحلةُ التصريف برنامجَ الكائن القابل للنقل، ويمكن استخدام هذا البرنامج في مواضع أخرى دون الحاجة إلى إعادة تصريفه مرّة أخرى. لإيقاف العملية بعد خطوة التجميع، استخدم الخيار-c: g++ -Wall -ansi -c prog.cpp الربط Linking الرابط (Linker) هو الذي ينتج مخرجات التصريف النهائية من كائنات الملفّات التي أنتجها المجمّع (Assembler)، وقد تكون المُخرجات إمّا مكتبة مشتركة (أو ديناميكية، رغم تشابه الأسماء، إلا أنّها تختلف عن المكتبات الساكنة المذكورة آنفًا)، أو يمكن أن تكون ملفّا تنفيذيًا. ويربط جميع الكائنات عن طريق استبدال العناوين الصحيحة بالمراجع التي تشير إلى رموز غير مُعرَّفة (undefined symbols). يمكن تعريف تلك الرموز في ملفّات كائنات أخرى أو في مكتبات أخرى، وإذا عُرِّفت في مكتبات غير المكتبة القياسية، فعليك إخبار الرابط بذلك. وتكون الأخطاء الأكثر شيوعًا في هذه المرحلة هي التعاريف المفقودة أو التعاريف المكرّرة، والأولى تعني أنّه إمّا أنّ التعاريف غير موجودة (أي أنّها غير مكتوبة)، أو أنّ ملفات الكائنات أو المكتبات التي عُرِّفت فيها لم تُعط للرابط، أمّا الأخطاء الأخيرة فهي واضحة: إذ أنّها تعني أنّ نفس الرمز قد عُرِّف في ملفّين أو مكتبتين مختلفتين. التصريف عبر Code::Blocks (واجهة رسومية) نزِّل Code::Blocks وثبِّته، وإذا كنت تستخدم ويندوز فاحرص على اختيار الملفّ الذي يحتوي اسمُه على mingw، إذ لا تثبّت الملفّات الأخرى أي مُصرِّفات. افتح Code::Blocks وانقر على "Create a new project": اختر "Console application" وانقر على "Go": انقر على"Next"، واختر "C++"، انقر على "Next"، اختر اسمًا لمشروعك، واختر مجلدًا لحفظه، ثمّ انقر على "Next" ثمّ على "Finish". يمكنك الآن تعديل وتصريف شيفرتك، ستجد شيفرة افتراضية تطبع السلسلة النصية "Hello world!" في سطر الأوامر. اضغط على أحد الأزرار الثلاثة compile/run في شريط الأدواتل تصريف و/أو تشغيل البرنامج: للتصريف دون تشغيل، اضغط على ، وللتشغيل دون التصريف مرّة أخرى، اضغط على ، وللتصريف ثمّ التشغيل اضغط على . تصريف وتشغيل البرنامج الافتراضي "مرحبا العالم!" سيُنتِج الخرج التالي: مواصفات الربط Linkage specifications تخبر مواصفات الربط (linkage specification) المُصرِّفَ بأن يصرِّف التصريحات بطريقة تسمح بربطها بالتصريحات المكتوبة بلغة أخرى، مثل C. معالج الإشارات الخاصّ بأنظمة التشغيل المشابهة ليونيكس بما أن معالج الإشارة (signal handler) يُستدعى عن طريق النواة (kernel) باستخدام صيغة الاستدعاء في لغة C، فيجب أن نطلب من االمُصرِّف أن يستخدم تلك الصيغة عند تصريف الدالّة. volatile sig_atomic_t death_signal = 0; extern "C" void cleanup(int signum) { death_signal = signum; } int main() { bind(...); listen(...); signal(SIGTERM, cleanup); while (int fd = accept(...)) { if (fd == -1 && errno == EINTR && death_signal) { printf("Caught signal %d; shutting down\n", death_signal); break; } // ... } } إنشاء مكتبة بلغة C متوافقة مع C++ يمكن في العادةً تضمين ترويسات مكتبة C في برامج C++، لأنّ معظم التصريحات صالحة في كلا اللغتين. على سبيل المثال، انظر ملف foo.h التالي: typedef struct Foo { int bar; } Foo; Foo make_foo(int); سيصرَّف تعريف make_foo بشكل منفصل ويُوزّع مع الترويسة على هيئة كائن. يمكن لبرنامج C++ أن يُضمِّن ملف الترويسة foo.h عبر التعبير #include <foo.h>، لكن لن يستطيع المُصرِّف أن يعرف أنّ الدالّة make_foo قد عُرِّفت وفق صيغة مكتوبة بلغة C، وسيحاول البحث عنه باسم آخر، وسيفشل في إيجاده. وحتى لو استطاع إيجاد تعريف make_foo في المكتبة، فليست كل المنصّات تستخدم نفس صيغات الاستدعاء في C و C++، وسوف يستخدم مصرّف C++ صيغة الاستدعاء الخاصة بـ C++ عند استدعاء make_foo، وهذا من شأنه أن يتسبّب في حدوث خطأ تجزئة (segmentation fault) في حال كان يُتوقّع أن تُستدعى make_foo وفق صيغة الاستدعاء في C. يمكن حلّ هذه المشكلة عبر تغليف (wrap) كافة التصريحات في ترويسة في كتلة extern "C"، انظر .. #ifdef __cplusplus extern "C" { #endif typedef struct Foo { int bar; } Foo; Foo make_foo(int); #ifdef __cplusplus } /* "extern C" نهاية الكتلة */ #endif والآن، عند تضمين foo.h من برنامج C، سيظهر كتصريح عادي، أمّا عند تضمين foo.h من برنامج مكتوب بلغة C++، فسيكون make_foo داخل كتلة extern "C"، وسيعرف المصرّفُ كيف يبحث عن الأسماء، وسيستخدم صيغة الاستدعاء في C. هذا الدرس جزء من سلسلة دروس عن C++. ترجمة -بتصرّف- للفصل Chapter 138: Compiling and Building والفصل Chapter 134: Linkage specifications من كتاب C++ Notes for Professionals1 نقطة
-
تُنفَّذ العمليات البتية (أو الثنائية) على مستوى البت من البيانات وذلك باستعمال العوامل التالية: | - عامِل OR البِتِّي (Bitwise OR) إليك المثال التالي: int a = 5; // 0101b (0x05) int b = 12; // 1100b (0x0C) int c = a | b; // 1101b (0x0D) std::cout << "a = " << a << ", b = " << b << ", c = " << c << std::endl; يكون ناتج الشيفرة السابقة هو: a = 5, b = 12, c = 13 وسبب ذلك أن عامِل OR يعمل على مستوى البِتّْ (bit level)، ويستخدم جدول الصحة البوليني أو المنطقي (Boolean Truth Table) التالي: true OR true = true true OR false = true false OR false = false عندما يُطبق عامل OR على القيمتين البتّيّتين a (أي 0101) و b (أي 1100) ، سنحصل على القيمة البتّيّة 1101: int a = 0 1 0 1 int b = 1 1 0 0 | --------- int c = 1 1 0 1 لا يغير عامِل OR القيم الأصلية للمعامَلات إلا في حالة استخدام عامِل الإسناد البِتِّي المركَّب =| (Bitwise Assignment Compound Operator) لإسناد تغيير تلك القيم: int a = 5; // 0101b (0x05) a |= 12; // a = 0101b | 1101b ^ - عامل XOR البِتِّي (Bitwise XOR) إليك المثال التالي: int a = 5; // 0101b (0x05) int b = 9; // 1001b (0x09) int c = a ^ b; // 1100b (0x0C) std::cout << "a = " << a << ", b = " << b << ", c = " << c << std::endl; يكون ناتج الشيفرة السابقة: a = 5, b = 9, c = 12 وسبب ذلك أن عامِل XOR يعمل على مستوى البِتّْ (bit level)، ويستخدم جدول الصحة البولياني التالي: true OR true = false true OR false = true false OR false = false لاحظ أنه في عملية XOR، يكون لدينا true XOR true = false، على خلاف العمليتين AND و OR، إذ تحققان: true AND/OR true = true، وذلك هو وصف الطبيعة الحصرية لعملية XOR. عندما يُطبّق عامِل XOR على القيمتين البتّيّتين a (أي 0101) و b (أي 1001) ، سنحصل على القيمة البتّيّة 1101: int a = 0 1 0 1 int b = 1 0 0 1 ^ --------- int c = 1 1 0 0 لا يغير عامِل XOR القيم الأصلية للمعامَلات إلا في حالة استخدام عامِل الإسناد البِتِّي المركَّب =^ (Bitwise Assignment Compound Operator) لإسناد تغيير تلك القيم: int a = 5; // 0101b (0x05) a ^= 9; // a = 0101b ^ 1001b يمكن استخدام عامِل XOR بعدة طرق، وغالبًا ما يُستخدَم في عمليات البتات المُقنّعة (bit mask operations) لتشفير البيانات وضغطها. لاحظ أن المثال التالي توضيحي، ولا ينبغي أن يُستخدم في الشيفرة النهائية (هناك طرق أفضل لتحقيق نفس النتيجة، مثل std::swap()). تستطيع استخدام عملية XOR لمبادلة متغيرين: int a = 42; int b = 64; // XOR المبادلة عبر a ^= b; b ^= a; a ^= b; std::cout << "a = " << a << ", b = " << b << "\n"; لإنجاز هذا، تحتاج إلى التحقق من إمكانية استخدامه. void doXORSwap(int & a, int & b) { // ينبغي إضافة عبارة للتحقق من أنك لا تبادل المتغير مع نفسه // وإلا ستجعل القيمة صفرية if ( & a != & b) { // XOR مبادلة a ^= b; b ^= a; a ^= b; } } ورغم أنها تبدو طريقة مناسبة للعزل إلا أنها ليست مفيدة في شيفرة الإنتاج، ذلك أن xor ليس عملية منطقية أساسية، بل مزيج من عمليات أخرى: a^c=~(a&c)&(a|c). كذلك ، فإن المُصرفات التي صدرت بعد 2015 أتاحت إسناد قيم ثنائية إلى المتغيرات: int cn=0b0111; & - عامل AND البتّيّ إليك المثال التالي: int a = 6; // 0110b (0x06) int b = 10; // 1010b (0x0A) int c = a & b; // 0010b (0x02) std::cout << "a = " << a << ", b = " << b << ", c = " << c << std::endl; يكون ناتج الشيفرة السابقة: a = 6, b = 10, c = 2 وسبب ذلك أن عامل AND يعمل على مستوى البت، ويستخدم جدول الصحة البوليني التالي: TRUE AND TRUE = TRUE TRUE AND FALSE = FALSE FALSE AND FALSE = FALSE عندما يُطبق عامِل AND على القيمتين البتّيّتين a (أي 0110) و b (أي 1010) ، سنحصل على القيمة البتّيّة 0010: int a = 0 1 1 0 int b = 1 0 1 0 & --------- int c = 0 0 1 0 لا يغير عامِل AND قيمة المعامَل الأصلي إلا عند استخدام عامِل الإسناد البِتِّي المركب &=: int a = 5; // 0101b (0x05) a &= 10; // a = 0101b & 1010b << - عملية الإزاحة اليسارية (left shift) إليك المثال التالي: int a = 1; // 0001b int b = a << 1; // 0010b std::cout << "a = " << a << ", b = " << b << std::endl; يكون ناتج ذلك هو: a = 1, b = 2 وتفسير ذلك أن عامِل الإزاحة اليساري يزيح بتَّات القيمة الموجودة على الجانب الأيسر (a) بالعدد المحدد على اليمين (1)، مع ملأ البتات الأقل أهمية (least significant bits) بأصفار، فمثلًا في حال إزاحة قيمة العدد 5 (تمثيله الثنائي 0000 0101) إلى اليسار أربعة منازل (على سبيل المثال 5 <<4)، سوف نحصل على القيمة 80 (تمثيلها الثنائي 0101 0000). قد تلاحظ أنّ إزاحة قيمة إلى اليسار بمنزلة واحدة يكافئ ضرب تلك القيمة بالعدد 2، على سبيل المثال: int a = 7; while (a < 200) { std::cout << "a = " << a << std::endl; a <<= 1; } a = 7; while (a < 200) { std::cout << "a = " << a << std::endl; a *= 2; } ولكن تجدر الإشارة إلى أنّ عملية الإزاحة اليسارية سوف تزيح جميع البتات إلى اليسار، بما في ذلك بتّ الإشارة، انظر إلى المثال التالي: int a = 2147483647; // 0111 1111 1111 1111 1111 1111 1111 1111 int b = a << 1; // 1111 1111 1111 1111 1111 1111 1111 1110 std::cout << "a = " << a << ", b = " << b << std::endl; سيكون الناتج: a = 2147483647, b = -2 وفي حين أنّ بعض المُصرِّفات ستعيد نتائج قد تبدو متوقعة غير أنه يجب ملاحظة أن إزاحة عددٍ ذا إشارة (signed number) بحيث تتأثر بتة الإشارة (sign bit)، فستكون النتيجة غير محددة (undefined). كذلك في حال كان عدد البتات التي ترغب في إزاحتها عددًا سالبًا، أو كان أكبر من عدد البتات التي يمكن أن يحملها النوع الموجود على اليسار، انظر: int a = 1; int b = a << -1; // سلوك غير محدد char c = a << 20; // سلوك غير محدد لا تغير الإزاحة البتّيّة اليسارية قيمة المعامَلات إلا في حال استخدام عامِل الإسناد البِتِّي المركب <<=: int a = 5; // 0101b a <<= 1; // a = a << 1; >> - الإزاحة اليمينية (right shift) إليك المثال التالي: int a = 2; // 0010b int b = a >> 1; // 0001b std::cout << "a = " << a << ", b = " << b << std::endl; سيكون الناتج: a = 2, b = 1 وتفسير ذلك أن عامِل الإزاحة اليميني يزيح بتِّات القيمة الموجودة على الجانب الأيسر (a) بالعدد المحدد على اليمين (1)؛ وتجدر الإشارة إلى أنه رغم أنّ عملية الإزاحة اليمينية قياسية، فإنّ ما يحدث لبتات الإزاحة اليمينية في الأعداد السالبة ذات الإشارة (signed negative number) يختلف بحسب الاستخدام، ومن ثم لا يمكن ضمان كونها قابلة للنقل (portable)، انظر: int a = -2; int b = a >> 1; // على المصرّف b تعتمد قيمة كذلك سيكون ذلك السلوك غير محدد إن كان عدد البتات التي ترغب في إزاحتها سالبًا، على سبيل المثال: int a = 1; int b = a >> -1; // سلوك غير محدد لا يغيّر عامل الإزاحة اليميني قيمة المعامَل الأصلي إلا في حال استخدام عامِل الإسناد البِتِّي المركب >>=: int a = 2; // 0010b a >>= 1; // a = a >> 1; هذا الدرس جزء من سلسلة دروس عن C++. ترجمة -وبتصرّف- للفصل Chapter 5: Bit Operators من كتاب C++ Notes for Professionals اقرأ أيضا الدرس 6: معالجة البتات والتلاعب بها الدرس 4: حسابيّات الأعداد العشرية1 نقطة
-
يفترض كثير من المبرمجين خطأً أن الشيفرة التالية ستعمل كما هو مخطط لها: float total = 0; for (float a = 0; a != 2; a += 0.01f) { total += a; } إذ يفترض المبرمج المبتدئ أن تلك الشيفرة ستجمع كل الأعداد الموجودة في النطاق المحصور بين 0، 0.01، 0.02، 0.03، … ، 1.97، 1.98، 1.99، للحصول في النهاية على 199 كنتيجة، وهي الإجابة الصحيحة. لكن لا يحدث ذلك لسببين: لن ينتهي البرنامج أبدًا بهيئته الحالية، لأنّ a لن تساوي أبدًا القيمة 2، والحلقة التكرارية لن تنتهي. إذا عدّلنا شرط الحلقة التكرارية وجعلناه a < 2، فإنّ الحلقة التكرارية ستنتهي، لكن الإجمالي سيكون شيئًا غير 199 التي نريدها، وعلى الأغلب ستحصل على 201 على الأجهزة المتوافقة مع معيار IEEE754. سبب حدوث ذلك هو أنّ الأعداد العشرية (Floating Point Numbers) تمثِّل القيم بشكل تقريبي وحسب. كما في المثال التالي: double a = 0.1; double b = 0.2; double c = 0.3; if (a + b == c) // IEEE754 لن يُطبع هذا على الأجهزة المتوافقة مع std::cout << "This Computer is Magic!" << std::endl; else std::cout << "This Computer is pretty normal, all things considered." << std::endl; برغم أننا كمبرمجين نرى ثلاثة أعداد مكتوبة وفق النظام العشري، إلا أن المصرِّف (Compiler) -والحاسوب الذي يعمل عليه- لا يرى إلا أرقامًا ثنائية، لذا يجب أن تخزَّن تلك الأعداد في صيغ تقريبية غير دقيقة كما في حالة العدد 1/3 إذ يخزن على الصورة 0.3333333333 في النظام العشري، وذلك لأن 0.1 و 0.2 و 0.3 تتطلب قسمة تامة على 10، وهذا يسير في النظام العشري لكنه مستحيل في النظام الثنائي. // الأعداد العشرية في نظام 64-بت لديها 53 خانة رقمية، بما في ذلك الجزء الصحيح من العدد double a = 0011111110111001100110011001100110011001100110011001100110011010; // تمثيل تقريبي للعدد 0.1 double b = 0011111111001001100110011001100110011001100110011001100110011010; // تمثيل تقريبي للعدد 0.2 double c = 0011111111010011001100110011001100110011001100110011001100110011; // تمثيل تقريبي للعدد 0.3 double a + b = 0011111111010011001100110011001100110011001100110011001100110100; // 0.3 لاحظ أن هذا العدد لا يساوي هذا الدرس جزء من سلسلة دروس عن C++. ترجمة -وبتصرّف- للفصل Chapter 4: Floating Point Arithmetic من كتاب C++ Notes for Professionals اقرأ أيضًا الدرس 5: العمليات البتِّيّة (Bit Operators) الدرس 3: أسبقية العمليات1 نقطة
-
العمليات المنطقية العامل المنطقي && له الأسبقية على ||، وذلك يعني أن الأقواس توضع لتقييم ما سيتم تقييمه مع غيره، وتستخدم C++ تقييم الدارة القصيرة (short-circuit evaluation) في تقييم && و || من أجل تجنّب إجراء العمليات غير الضرورية. أيضًا، إذا أعاد الجانب الأيسر من || القيمة true، فلن تكون هناك حاجة إلى تقييم الجانب الأيمن. في المثال التالي، سنقيّم ثلاثة قيم منطقية بالعامِليْن || و && من أجل توضيح الأسبقية، لا تعني الأسبقية أن && ستقيَّم أولًا وإنما توضح أين ستوضع الأقواس: #include <iostream> #include <string> using namespace std; bool True(string id) { cout << "True" << id << endl; return true; } bool False(string id) { cout << "False" << id << endl; return false; } int main() { bool result; // حالة 1 result = False("A") || False("B") && False("C"); // eq. False("A") || (False("B") && False("C")) //FalseA //FalseB // حالة 2 result = True("A") || False("B") && False("C"); // eq. True("A") || (False("B") && False("C")) cout << result << " :=====================" << endl; //TrueA } في تقييم الدارة القصيرة، يجري تخطي C في الحالة الأولى، فبما أن A خطأ فيجب تقييم الجانب الأيمن من العامِل ||؛ تكون B خطأ أيضًا، لذا ليس هنالك حاجة إلى تقييم C مهما كان تقييمها سواءً خطأ False أم صحيح True. في الحالة الثانية، يجري تخطي B و C وفقًا لتقييم الدارة القصيرة، إذ قيمة A هي True صحيحة فلا داع لتقييم الجانب الأيمن من المعامل ||. إن كان للعامِل || أسبقية على && فإن التقييم المكافئ يكون: (True("A") || False("B")) && False("C") والذي سيطبع TrueA للجزء الأول الموضوع بين أقواس والذي سيتخطى B فقط آنذاك في تقييم الدارة القصيرة ولكن بوجود العامل && في الطرف الأيمن من الأقواس، ستكون النتيجة النهائية FalseC. بما أنّ الأقواس موضوعة بشكل مختلف، فإنّ الأجزاء التي ستُقيّم ستكون مختلفة ما يجعل النتيجة النهائية في هذه الحالة هي False، لأن تقييم C هو False. العاملان AND و OR لهما الأسبقية المعتادة في C++، أي أنّ AND تسبق OR دومًا. // يمكنك القيادة برخصة أجنبية لستِّين يوما bool can_drive = has_domestic_license || has_foreign_license && num_days <= 60; هذه الشيفرة مكافئة لما يلي: // يمكنك القيادة برخصة أجنبية لستّين يوما bool can_drive = has_domestic_license || (has_foreign_license && num_days <= 60); لا تؤدي إضافة الأقواس إلى تغيير السلوك، ولكنها تحسّن إمكانية القراءة، وترفع اللُّبس عن نية كاتب الشيفرة. العمليات الأحادية تُطبَّق العمليات الأحادية على الكائن الذي استُدعيت عليه وسميت أحادية لأن العملية تطبَّق على طرف واحد فقط باستعمال عامل أحادي (Unary Operator)، ولها أسبقية عالية أي تأخذ الأولوية دومًا ضمن المعاملات. عند استخدام العامِل الأحادي بعد الكائن -أي postfix-، فلن يُتَّخذ إجراء إلا بعد تقييم العملية بأكملها، وهذا مفيد في بعض العمليات الحسابية. إليك المثال التالي: int a = 1; ++a; // 2 a--; // 1 int minusa = -a; // -1 bool b = true; !b; // true a = 4; int c = a++/2; // 4 / 2 = 2 cout << a << endl; // 5 int d = ++a / 2; // 6 / 2 = 3 int arr[4] = {1,2,3,4}; إليك مثال آخر أكثر تتعقيدًا حول تطبيق عملية أحادية باستعمال العامل ++ على المؤشرات: int arr[4] = {1,2,3,4}; int * ptr1 = & arr[0]; int * ptr2 = ptr1++; std::cout << * ptr1++ << std::endl; // 2 // قبل زيادته arr[0] الحصول على قيمة العنصر int e = arr[0]++; std::cout << e << std::endl; // 1 std::cout << * ptr2 << std::endl; // 2 يشير المؤشر ptr1 إلى عنوان [arr[0 والتي تخزن القيمة 1 بينما يشير المؤشر ptr2 إلى المؤشر prt1 ثم يزيد من قيمة الأخير مقدار 1 ليشير بذلك إلى العنصر [arr[1. العمليات الحسابية العمليات الحسابية في C++ لها نفس الأسبقية التي لها في الرياضيات. فعمليتا الضرب والقسمة لهما تجميع يساري (left associativity) -بمعنى أنهما تُقيَّمان من اليسار إلى اليمين-، ولهما أسبقية أعلى من الجمع والطرح، واللتان لهما تجميع يساري كذلك. كما يمكننا أيضًا التحكم في أسبقية التعابير باستخدام الأقواس () كما نفعل في الرياضيات العادية. إليك المثال التالي: // 4 pi R^3 - 4 pi r^3 = حجم السطح الكروي double vol = 4.0 * pi * R * R * R / 3.0 - 4.0 * pi * r * r * r / 3.0; // الإضافة int a = 2 + 4 / 2; // = 2+(4/2) = 4 int b = (3 + 3) / 2; // = (3+3)/2 = 3 // مع الضرب int c = 3 + 4 / 2 * 6; // = 3+((4/2)*6) = 15 int d = 3 * (3 + 6) / 9; // = (3*(3+6))/9 = 3 // القسمة والباقي int g = 3 - 3 % 1; // = 3 % 1 = 0 3 - 0 = 3 int h = 3 - (3 % 1); // = 3 % 1 = 0 3 - 0 = 3 int i = 3 - 3 / 1 % 3; // = 3 / 1 = 3 3 % 3 = 0 3 - 0 = 3 int l = 3 - (3 / 1) % 3; // = 3 / 1 = 3 3 % 3 = 0 3 - 0 = 3 int m = 3 - (3 / (1 % 3)); // = 1 % 3 = 1 3 / 1 = 3 3 - 3 = 0 هذا الدرس جزء من سلسلة دروس عن C++. ترجمة -وبتصرّف- للفصل Chapter 3: operator precedence من كتاب C++ Notes for Professionals اقرأ أيضا الدرس 4: حسابيات الأعداد العشرية الدرس 2: القيم مصنَّفة النوع1 نقطة
-
القيم مصنَّفة النوع (Literals) هي عبارات تدل على ثابت تشير صياغته المحرفية إلى نوعه وقيمته، فمثلًا 42 هي قيمة مصنَّفة النوع بينما x ليست كذلك لأنك تحتاج إلى أن تنظر إلى تصريحها (declaration) لتعرف نوعها وتطالع الأسطر السابقة من الشيفرة لتعرف قيمتها. غير أن C++11 أتاحت للمستخدم إضافة قيم مصنَّفة النوع (User-Defined Literals)، وتُستخدم كاختصار لاستدعاءات الدوال. سنستَعرض في هذا الدرس بعض القيم مصنَّفة النوع في C++، بدءًا بالقيمة this، ثم نستعرض بقية القيم في الأقسام التالية. الكلمة المفتاحية this تشير الكلمة المفتاحية this داخل دالة تابعة (member function) إلى نسخة من الصنف التي استدعيّت الدالة عليها، ولا يمكن استخدامها -أي this- في دالة تابعة ساكنة (static member function). struct S { int x; S & operator = (const S & other) { x = other.x; // تعيد مرجعًا إلى الكائن الذي أسندَت إليه return *this; } }; يعتمد نوع this على خاصية التأهيل الخاص بالتابع -سواء التأهيل المتطاير volatile أو الثابت constant- (بالإنجليزية اختصارًا: cv-qualification). فمثلًا، إن كان التابع X::f ثابتًا (const)، فإنّ نوع this داخل f سيكون مؤشرًا ثابتًا إلى X (أي const X*)، لذلك لا يمكن استخدام this لتعديل الحقول غير الساكنة (non-static data members) من داخل دالة const تابعة. وبالمثل، يكتسب this التأهيل المتطاير (volatile qualification) من الدالة التي يظهر فيها. الإصدار ≥ C++11 يمكن أيضًا استخدام this في المُهيِّئ المعقوص (brace-or-equal-initializer) للحقول غير الساكنة. struct S; struct T { T(const S * s); // ... }; struct S { // ... T t { this }; }; this عبارة عن قيمة يمينيّة (rvalue)، أي أنّها تعبير لا يمكن أن يكون إلا في الطرف الأيمن من أيّ عملية إسناد (assignment)، لذلك لا يمكن إسناد قيمة لها. الأعداد الصحيحة قيمةً ونوعًا (Integer literal) تأخذ صياغة العدد الصحيح أحد الأشكال التالية: نوع عشري (decimal-literal): وهو عدد مؤلف من رقم عشري يخالف الصفر (1، 2، 3، 4، 5، 6، 7، 8، 9)، متبوعًا برقم عشريّ واحد أو أكثر (0، 1، 2، 3، 4، 5، 6، 7، 8، 9)، مثلًا: int d = 42; نوع ثماني (octal-literal): وهو عدد يبدأ بالرقم صفر (0) متبوعًا برقم ثُمانيّ (octal) واحد أو أكثر (0، 1، 2، 3، 4، 5، 6، 7)، مثلًا: int o = 052; نوع ست عشري (hex-literal): وهو تسلسل الأحرف 0x أو 0X متبوعًا برقم واحد أو أكثر من الأرقام الست عشرية (0, 1, 2, 3, 4, 5, 6, 7, 8, 9, a, A, b, B, c, C, d, D, e, E, f, F)، مثلًا: int x = 0x2a; int X = 0X2A; نوع ثنائي (binary-literal): (منذ C ++ 14) هو تسلسل الأحرف 0b أو 0B متبوعًا برقم ثنائي واحد أو أكثر (0، 1)، مثلا: int b = 0b101010; الأعداد الصحيحة الملحوقة غير المؤشّرة (unsigned-suffix)، قد تحتوي على أحد أو كلا الأمرين التاليين -قد يظهرا بدون ترتيب محدد في حالة وجودهما معًا: الأعداد الملحوقة غير المُؤشرة (unsigned-suffix): هي أعداد ملحوقة بالحرف u أو U، مثلا: unsigned int u_1 = 42u; الأعداد الملحوقة الطويلة (long-suffix): هي أعداد ملحوقة بالحرف l أو L، أو الأعداد الملحوقة الطويلة المزدوجة (long-long-suffix)، وهي أعداد ملحوقة بالحرفين ll أو الحرفين LL (منذ C++11) تأخذ المتغيرات التالية نفس القيمة: unsigned long long l1 = 18446744073709550592ull; // C++11 unsigned long long l2 = 18'446'744'073'709'550'592llu; // C++14 unsigned long long l3 = 1844'6744'0737'0955'0592uLL; // C++14 unsigned long long l4 = 184467'440737'0'95505'92LLU; // C++14 ملاحظات الأحرف الموجودة في الأعداد الصحيحة غير حساسة للحالة (case-insensitive): فتمثل الصيغتان 0xDeAdBaBeU و 0XdeadBABEu نفس العدد (الاستثناء الوحيد هو الأعداد الملحوقة الطويلة المزدوجة، والتي يمكن أن تكون إما ll أو LL، ولكن ليس lL أو Ll). لا توجد صياغة لأعداد سالبة، فمثلًا تطبق تعابير مثل -1 عامل السالب الأحادي (unary minus operator) على القيمة المُمثّلة بالقيمة مصنفَّة النوع (Literal)، والتي قد تتضمن تحويلات ضمنية للنوع (implicit type conversions). في إصدارات C السابقة للإصدار C99 (ولكن ليس في C++)، يُسمح للقيم العشرية غير الملحوقة (unsuffixed decimal) من غير النوع long int أن تكون من النوع unsigned long int (عدد صحيح طويل عديم الإشارة). تتصرف جميع الثوابت الصحيحة المؤشَّرة (signed integer constants) عند استخدامها في التعبيرات الشرطية -مثل #if أو #elif- كما لو كانت من النوع std::intmax_t، فيما تتصرف الثوابت الصحيحة غير المؤشرة كما لو كانت من النوع std::uintmax_t. القيم المنطقية الكلمة المفتاحية true true هي كلمة مفتاحية تمثّل إحدى القيمتين المنطقيّتين المُمكنَتين للنوع bool. bool ok = true; if (!f()) { ok = false; goto end; } الكلمة المفتاحية false false هي كلمة مفتاحية تمثل إحدى القيمتين المنطقيّتين المُمكنتين للنوع bool. bool ok = true; if (!f()) { ok = false; goto end; } الكلمة المفتاحية nullptr الإصدار ≥ C++ 11 nullptr هي كلمة مفتاحية تمثل مؤشرًا ثابتًا فارغًا (null pointer constant)، ويمكن تحويلها إلى أيّ نوع من المؤشرات (Pointers) أو المؤشرات إلى الأعضاء (pointer-to-member)، وتعيد مؤشرًا فارغًا يشير إلى النوع الناتج. Widget* p = new Widget(); delete p; p = nullptr; // تفريغ المؤشر بعد الحذف لاحظ أنّ nullptr ليست مؤشرًا بحد ذاتها، وإنّما هي نوع أساسي معروف باسم std::nullptr_t. void f(int * p); template < class T > void g(T * p); void h(std::nullptr_t p); int main() { f(nullptr); // ok g(nullptr); // error h(nullptr); // ok } هذا الدرس جزء من سلسلة دروس عن C++. ترجمة -وبتصرّف- للفصل Chapter 2: Literals من كتاب C++ Notes for Professionals اقرأ أيضا الدرس 3: أسبقية العمليات الدرس 1: مدخل إلى C++1 نقطة
-
كما ذَكَرَنا سابقًا، الأسماء هي أحد أركان البرمجة الأساسية. تَتَضمَّن مسألة التَّصْريح (declaring) عن الأسماء واِستخدَامها الكثير من التفاصيل التي تَجنَّبنا غالبيتها إلى الآن، ولكن حان وقت الكشف عن كثير من تلك التفاصيل. حاول التركيز عمومًا على محتوى القسمين الفرعيين "التهيئة أثناء التصريح" و "الثوابت المسماة (named constants)"؛ لأننا سنُعيد الإشارة إليهما باستمرار. التهيئة (initialization) أثناء التصريح (declaration) عندما نُصرِّح عن مُتَغيِّر ما (variable declaration)، يُخصِّص الحاسوب مساحة من الذاكرة لذلك المُتَغيِّر، والتي ينبغي تَهيئتها (initialize) مبدئيًا، بحيث يَتَضمَّن ذلك المُتَغيِّر قيمة معينة قَبْل أي مُحاولة لاِستخدَامه ضِمْن تعبير (expression). عادةً ما ترى تَعْليمَة التَّصْريح (declaration) عن مُتَغيِّر محليّ معين (local variable) مَتبوعة بتَعْليمَة إِسْناد (assignment statement) لغرض تهيئة ذلك المُتَغيِّر مبدئيًا (initialization). على سبيل المثال: int count; // صرح عن المتغير count count = 0; // أسند القيمة 0 إلى المتغير count في الواقع، تستطيع تَضْمِين التهيئة المبدئية (initialization) للمُتَغيِّر بنفس تَعْليمَة التصريح (declaration statement)، أيّ يُمكِن اختصار التَعْليمَتين بالأعلى إلى التَعْليمَة التالية: int count = 0; // صرح عن المتغير count وأسند إليه القيمة 0 سيُنفِّذ الحاسوب ذلك على خطوتين: سيُصرِّح أولًا عن مُتَغيِّر اسمه count، ثم سيُسنِد القيمة صفر إلى ذلك المُتَغيِّر المُنشَئ للتو. ليس ضروريًا أن تَكُون قيمة المُتَغيِّر المبدئية ثابتة (constant)، وإِنما قد تَتَكوَّن من أي تعبير (expression). يُمكِنك أيضًا تهيئة عدة مُتَغيِّرات بتَعْليمَة تَّصْريح واحدة كالتالي: char firstInitial = 'D', secondInitial = 'E'; int x, y = 1; // صالح ولكن أسندت القيمة 1 إلى المتغير y فقط int N = 3, M = N+2; // صالح، هُيأت القيمة N قبل استخدامها لمّا كان المُتَغيِّر المُتحكِّم بحَلْقة معينة (loop control variable) غير مَعنِي بأي شيء يَقَع خارج تلك الحَلْقة عمومًا، كان من المنطقي التَّصْريح عنه ضِمْن ذلك الجزء من البرنامج الذي يَستخدِمُه فعليًا. ولهذا يَشيَع اِستخدَام التَعْليمَة المُختصرة بالأعلى ضِمْن حَلْقات التَكْرار for؛ حيث تُمكِّنك من التَّصْريح عن المُتَغيِّر المُتحكِّم بالحَلْقة، وتَهيئته مبدئيًا بمكان واحد ضِمْن الحَلْقة ذاتها. على سبيل المثال: for ( int i = 0; i < 10; i++ ) { System.out.println(i); } الشيفرة بالأعلى هي بالأساس اختصار لما يَلي: { int i; for ( i = 0; i < 10; i++ ) { System.out.println(i); } } أَضفنا زوجًا إضافيًا من الأقواس حول الحَلْقة -بالأعلى- للتأكيد على أن المُتَغيِّر i قد أصبح مُتَغيِّرًا محليًا (local) لتَعْليمَة الحَلْقة for، أيّ أنه لَمْ يَعُدْ موجودًا بعد انتهاء الحَلْقة. يُمكِنك أيضًا إجراء التهيئة المبدئية (initialize) للمُتَغيِّرات الأعضاء (member variable) أثناء تَّصْريحك عنها (declare)، كما هو الحال مع المُتَغيِّرات المحليّة (local variable). فمثلًا: public class Bank { private static double interestRate = 0.05; private static int maxWithdrawal = 200; . . // المزيد من المتغيرات والبرامج الفرعية . } عندما يُحمِّل مُفسِّر الجافا (Java interpreter) صنفًا معينًا، فإنه يُنشِئ أي مُتَغيِّر عضو ساكن (static member variable) ضِمْن ذلك الصنف، ويُهيِئ قيمته المبدئية (initialization). لمّا كانت تَعْليمَات التَّصْريح (declaration statements) هي النوع الوحيد من التَعْليمَات التي يُمكِن كتابتها خارج أيّ برنامج فرعي (subroutine)، وعليه فإن تَعْليمَات الإِسْناد (assignment statements) غير ممكنة الكتابة خارجها، كان لزامًا تهيئة المُتَغيِّرات الأعضاء الساكنة أثناء التَّصْريح عنها -إذا أردنا القيام بالتهيئة-، فهو في تلك الحالة ليس مُجرَّد اختصار لتَعْليمَة تَّصْريح (declaration) مَتبوعة بتَعْليمَة إِسناد (assignment statement) مثلما هو الحال مع المُتَغيِّرات المحليّة. لاحِظ عدم إِمكانية القيام بالتالي: public class Bank { private static double interestRate; // غير صالح! لا يمكن استخدام تعليمة إسناد خارج برنامج فرعي interestRate = 0.05; . . . لذلك، غالبًا ما يَحتوِي التَّصْريح عن مُتَغيِّر عضو (member variables) على قيمته المبدئية. وكما ذَكَرَنا سلفًا بالقسم الفرعي ٤.٢.٤، فإنه في حالة عدم تَخْصِيص قيمة مبدئية لمُتَغيِّر عضو معين (member variable)، تُسنَد إليه قيمة مبدئية افتراضية. على سبيل المثال، اِستخدَام التَعْليمَة static int count للتَّصْريح عن المُتَغيِّر العضو count هو مُكافِئ تمامًا للتَعْليمَة static int count = 0;. نستطيع أيضًا تهيئة مُتَغيِّرات المصفوفة (array variables) مبدئيًا. ولمّا كانت المصفوفة عمومًا مُكوَّنة من عدة عناصر وليس مُجرَّد قيمة وحيدة، تُستخدَم قائمة من القيم، مَفصولة بفاصلة (comma)، ومُحاطة بزوج من الأقواس؛ لتهيئة (initialize) المُتَغيِّرات من ذلك النوع، كالتالي: int[] smallPrimes = { 2, 3, 5, 7, 11, 13, 17, 19, 23, 29 }; تُنشِئ التَعْليمَة بالأعلى مصفوفة أعداد صحيحة (array of int)، وتَملؤها بالقيم ضِمْن القائمة. يُحدِّد عدد العناصر بالقائمة طول المصفوفة المُعرَّفة، أيّ أن طولها (length) -في المثال بالأعلى- يُساوِي ١٠. تَقْتصِر صيغة (syntax) تهيئة المصفوفات -يُقصَد بذلك قائمة العناصر- على تَعْليمَات التَّصْريح (declaration statement)، أي بينما نُصرِّح عن مُتَغيِّر مصفوفة (array variable)، ولا يُمكِن اِستخدَامها ضِمْن تَعْليمَات الإِسْناد (assignment statements). تستطيع أيضًا إنشاء مصفوفة باِستخدَام العَامِل new، ثم تَستخدِمها لتهيئة مُتَغيِّر مصفوفة (array variable). لاحِظ أن تلك الصيغة صالحة ضِمْن تَعْليمَات الإِسْناد أيضًا على العَكْس من الصيغة السابقة. على سبيل المثال: String[] nameList = new String[100]; ستَحتوِي جميع عناصر المصفوفة على القيمة الافتراضية في المثال بالأعلى. التصريح عن المتغيرات باستخدام var منذ الإصدار العاشر، تُوفِّر الجافا صيغة (syntax) جديدة للتَّصْريح عن المُتَغيِّرات، باستخدَام كلمة var، بشَّرْط تَخْصيص قيمة مبدئية ضِمْن تَعْليمَة التَّصْريح. تمتلك المُتَغيِّرات المُصرَّح عنها بتلك الطريقة نوعًا محدَّدًا، كأي مُتَغيِّر عادي آخر، ولكن بدلًا من تَحْديد ذلك النوع تحديدًا صريحًا، يعتمد مُصرِّف الجافا (Java compiler) على نوع القيمة المبدئية المُخصَّصة لتَحْديد نوع ذلك المُتَغيِّر. تَقْتصِر تلك الصيغة، مع ذلك، على المُتَغيِّرات المحليّة (local variables)، أي تلك المُصرَّح عنها ضِمْن برنامج فرعي (انظر القسم الفرعي ٤.٢.٤). اُنظر تَعْليمَة التَّصْريح التالية: var interestRate = 0.05; تُستخدَم التَعْليمَة بالأعلى لتعريف (define) مُتَغيِّر محليّ اسمه interestRate بقيمة مبدئية تُساوِي ٠,٠٥، ولمّا كانت القيمة المبدئية المُخصَّصة من النوع double، يَكُون نوع ذلك المُتَغيِّر هو double. بالمثل، للتَّصْريح عن مُتَغيِّر محليّ اسمه nameList من النوع String[]، يُمكِنك كتابة التالي: var nameList = new String[100]; تستطيع أيضًا اِستخدَام كلمة var أثناء التَّصْريح عن المُتَغيِّر المُتحكِّم بالحَلْقة (loop control variable) ضِمْن الحَلْقة for، كالتالي: for ( var i = 0; i < 10; i++ ) { System.out.println(i); } قد لا يبدو ذلك مفيدًا في الوقت الحالي، ولكنك ستُدرِك أهميته عندما نَتَعرَّض لما يُعرَف باسم الأنواع المُعمَّمة أو الأنواع المُحدَّدة بمعاملات نوع (parameterized types)، والتي تُعدّ أكثر تعقيدًا. سنتناول تلك الأنواع بكُلًا من القسم ٧.٢، والفصل العاشر. الثوابت المسماة (named constants) في بعض الأحيان، لا يَكُون هناك حاجة لتَعْديل قيمة المُتَغيِّر بَعْد تهيئتها مبدئيًا (initialize). على سبيل المثال، هُيَئ المُتَغيِّر interestRate بالأعلى، بحيث تَكُون قيمته المبدئية مُساوِية للقيمة ٠,٠٥. دَعْنَا نفْترِض أنه لا حاجة لتَعْديل تلك القيمة طوال فترة تَّنْفيذ البرنامج. في مثل تلك الحالات، قد يبدو المُتَغيِّر عديم الفائدة، ولهذا قد تَتَساءل، لماذا قد يُعرِّف (defining) المبرمج ذلك المُتَغيِّر من الأساس؟ في الواقع، يَلجأ المُبرمجين عادةً إلى تعريف تلك المُتَغيِّرات؛ بهدف إعطاء قيمة عددية معينة (٠,٠٥ في هذا المثال) اسمًا له مَغزى، فبخلاف ذلك، ستَكُون القيمة ٠,٠٥ بلا أي مَعنَى؛ فمن الأسهل عمومًا فِهم تَعْليمَة مثل principal += principal*interestRate; بالمُوازنة مع principal += principal*0.05. يُمكِن اِستخدَام المُبدِّل (modifier) final أثناء التَّصْريح عن مُتَغيِّر معين (variable declaration)؛ للتَأكُّد من اِستحالة تَعْديل القيمة المُخزَّنة بذلك المُتَغيِّر بمجرد تَهيئتها (initialize). إذا صَرَّحت، مثلًا، عن المُتَغيِّر العضو interestRate باستخدام التَعْليمَة التالية: public final static double interestRate = 0.05; فسيَستحِيل تَعْديل قيمة ذلك المُتَغيِّر interestRate بأيّ مكان آخر داخل البرنامج، فإذا حَاوَلت، مثلًا، اِستخدَام تَعْليمَة إِسْناد لتَعْديل قيمته، فإن الحاسوب سيُبلِّغ عن وجود خطأ في بناء الجملة (syntax error) أثناء تَصْرِيف (compile) البرنامج. من المنطقي عمومًا اِستخدَام المُبدِّل final مع مُتَغيِّر عام (public) يُمثِل قيمة "مُعدل الفائدة"؛ فبينما يُريد بنك معين الإعلان عن قيمة "مُعدل الفائدة"، فإنه، وبكل تأكيد، لا يُريد أن يَسمَح لأشخاص عشوائية بتَعْديل قيمة "مُعدل الفائدة". تَبرز أهمية المُبدِّل final عند اِستخدَامه مع المُتَغيِّرات الأعضاء، ولكنه مع ذلك، قابل للاِستخدَام مع كلا من المُتَغيِّرات المحليّة (local variables)، والمُعامِلات الصُّوريّة (formal parameters). يُطلَق مصطلح الثوابت المُسمَاة (named constants) على المُتَغيِّرات الأعضاء الساكنة (static member variable) المُصرَّح عنها باِستخدَام المُبدِّل final؛ لأن قيمتها تَظلّ تابثة طوال فترة تَّنْفيذ البرنامج. اِستخدَامك للثوابت المُسمَاة (named constants) قادر على تَحسِّين شيفرة البرامج بشكل كبير، وجَعْلها مَقروءة؛ فأنت بالنهاية تُعطِي أسماء ذات مَغزى للمقادير المُهمة بالبرنامج، ويُوصَى عمومًا بأن تَكُون تلك الأسماء مُكوَّنة بالكامل من حروف كبيرة (upper case letters)، بحيث يَفصِل محرف الشرطة السُفلية (underscore) بين الكلمات إذا ما لَزِم الأمر. على سبيل المثال، يُمكِن تعريف "مُعدَل الفائدة" كالتالي: public final static double INTEREST_RATE = 0.05; يَشيِع اِستخدَام نَمط التسمية بالأعلى عمومًا بأصناف الجافا القياسية (standard classes)، والتي تُعرِّف الكثير من الثوابت المُسمَاة. فمثلًا، تَعرَّضنا بالفعل للمُتَغيِّر Math.PI من النوع double المُعرَّف ضِمْن الصنف Math باِستخدَام المُبدِّلات public final static. بالمثل، يَحتوِي الصَنْف Color على مجموعة من الثوابت المُسمَاة (named constants) مثل: Color.RED و Color.YELLOW، والتي هي مُتَغيِّرات من النوع Color، مُعرَّفة باِستخدَام نفس مجموعة المُبدِّلات public final static. تُعدّ ثوابت أنواع التعداد (enumerated type constants)، والتي تَعرَّضنا لها خلال القسم الفرعي ٢.٣.٣، مثالًا آخرًا للثوابت المُسمَاة. يُمكِننا تعريف تعداد من النوع Alignment كالتالي: enum Alignment { LEFT, RIGHT, CENTER } يُعرِّف ذلك التعداد كُلًا من الثوابت Alignment.LEFT و Alignment.RIGHT و Alignment.CENTER. تقنيًا، يُعدّ Alignment صنفًا (class)، وتُعدّ تلك الثوابت (constants) الثلاثة أعضاء ساكنة عامة ونهائية (public final static) ضِمْن ذلك الصَنْف. في الواقع، يُشبِه تعريف نوع التعداد (enumerated type) بالأعلى تعريف ثلاثة ثوابت من النوع int كالتالي: public static final int ALIGNMENT_LEFT = 0; public static final int ALIGNMENT_RIGHT = 1; public static final int ALIGNMENT_CENTER = 2; وفي الحقيقة، كانت تلك الطريقة هي الأسلوب المُتبَع قبل ظهور أنواع التعداد (enumerated types)، وما تزال في الواقع مُستخدَمة بكثير من الحالات. فبِتَوفُّر ثوابت الأعداد الصحيحة (integer constants) بالأعلى، تستطيع الآن ببساطة تعريف مُتَغيِّر من النوع int، وإِسْناد واحدة من تلك القيم الثلاثة ALIGNMENT_LEFT و ALIGNMENT_RIGHT و ALIGNMENT_CENTER له؛ بهدف تمثيل أنواع مختلفة من التعداد Alignment. مع ذلك هناك مشكلة، وهي عدم إِطلاع الحاسوب على نيتك باِستخدَام ذلك المُتَغيِّر لتمثيل قيمة من النوع Alignment، لذا فإنه لن يَعترِض إذا حاولت إِسْناد قيمة هي ليست ضِمْن تلك القيم الثلاثة الصالحة. في المقابل، ومع ظهور أنواع التعداد (enumerated type)، فإنه إذا كان لديك مُتَغيِّر من نوع التعداد Alignment، فيُمكِنك فقط إِسْناد إحدى تلك القيم الثابتة المُدرجة بتعريف التعداد إليه، وأي محاولة لإِسْناد قيم آخرى غَيْر صالحة ستُعدّ بمثابة خطأ ببناء الجملة (syntax error) سيَكْتَشفها الحاسوب أثناء تَصْرِيف (compile) البرنامج. يُوفِّر ذلك نوعًا من الأمان الإضافي، وهو أحد أهم مميزات أنواع التعداد (enumerated types). يُسهِل استخدام الثوابت المُسمَاة (named constants) من تَعْديل قيمة الثابت المُسمَى. بالطبع، لا يُقصَد بذلك تَعْديلها أثناء تَّنْفيذ البرنامج، وإنما بين تَّنْفيذ معين والذي يليه، أيّ تَعْديل القيمة داخل الشيفرة المصدرية ذاتها ثم إعادة تَصرِيف (recompile) البرنامج. لنُعيد التفكير بمثال "معدل الفائدة"، غالبًا ما ستَكُون قيمة "مُعدَل الفائدة" مُستخدَمة أكثر من مرة عبر شيفرة البرنامج. لنفْترِض أن البنك قد عَدَّل قيمة "معدل الفائدة"، وعليه ينبغي تَعْديلها بالبرنامج. إذا كانت القيمة ٠,٠٥ مُستخدَمة بشكل مُجرَّد ضِمْن الشيفرة، فسيَضطرّ المبرمج لتَعقُّب جميع الأماكن المُستخدِمة لتلك القيمة بحيث يُعدِّلها إلى القيمة الجديدة. يَصعُب القيام بذلك خصوصًا مع احتمالية اِستخدَام القيمة ٠,٠٥ داخل البرنامج لأهداف مختلفة غير "مُعدَل الفائدة"، بالإضافة إلى احتمالية اِستخدَام القيمة ٠,٠٢٥ مثلًا لتمثيل نصف قيمة "مُعدَل الفائدة". في المقابل، إذا كنا قد صَرَّحنا عن الثابت المُسمَى INTEREST_RATE ضِمْن البرنامج، واستخدَمناه بصورة مُتَّسقِة عبر شيفرة البرنامج بالكامل، فسنحتاج إلى تعديل سطر وحيد فقط هو سَطْر التهيئة المبدئية لذلك الثابت. لنأخذ مثالًا آخر من القسم السابق، سنُعيد خلاله كتابة نسخة جديدة من البرنامج RandomMosaicWalk. ستَستخدِم تلك النسخة عدة ثوابت مُسمَاة (named constants) بهدف تمثيل كُلًا من عدد الصفوف، وعدد الأعمدة، وحجم كل مربع صغير بنافذة الصَنْف mosaic. سيُصرَّح عن تلك الثوابت (constants) الثلاثة كمُتَغيِّرات أعضاء ساكنة نهائية (final static member) كالتالي: final static int ROWS = 20; // عدد صفوف النافذة final static int COLUMNS = 30; // عدد أعمدة النافذة final static int SQUARE_SIZE = 15; // حجم كل مربع بالنافذة عُدِّل أيضًا باقي البرنامج بحرِص بحيث يتلائم مع الثوابت المُسمَاة (named constants) المُعرَّفة، فمثلًا، لفَتْح نافذة Mosaic بالنسخة الجديدة من البرنامج، يُمكِن استخدام التَعْليمَة التالية: Mosaic.open(ROWS, COLUMNS, SQUARE_SIZE, SQUARE_SIZE); ليس من السهل دومًا العُثور على جميع تلك الأماكن التي يُفْترَض بها اِستخدَام ثابت مُسمَى معين. لذا، انتبه! ففي حالة عدم اِستخدَامك لثابت مُسمَى معين بصورة مُتَّسقِة عبر شيفرة البرنامج بالكامل، فأنت تقريبًا قد أَفسدت الهدف الأساسي منه. لذلك يُنصَح عادة بتجربة البرنامج عدة مرات، بحيث تَستخدِم قيمة مختلفة للثابت المُسمَى (named constant) في كل مرة؛ لاِختبار كَوْنه يَعَمَل بصورة سليمة بجميع الحالات. اُنظر النسخة الجديدة من البرنامج كاملة RandomMosaicWalk2 بالأسفل. لاحظ كيفية اِستخدَام الثابتين ROWS و COLUMNS داخل البرنامج randomMove() عند تحريكه للتشويش (disturbance) من إحدى حواف النافذة mosaic إلى الحافة المضادة. لم تُضاف التعليقات (comments) لغرض تَوْفِير المساحة. public class RandomMosaicWalk2 { final static int ROWS = 20; // عدد الصفوف بالنافذة final static int COLUMNS = 30; // عدد الأعمدة بالنافذة final static int SQUARE_SIZE = 15; // حجم كل مربع بالنافذة static int currentRow; // رقم الصف المعرض للتشويش static int currentColumn; // رقم العمود المعرض للتشويش public static void main(String[] args) { Mosaic.open( ROWS, COLUMNS, SQUARE_SIZE, SQUARE_SIZE ); fillWithRandomColors(); currentRow = ROWS / 2; // ابدأ بمنتصف النافذة currentColumn = COLUMNS / 2; while (true) { changeToRandomColor(currentRow, currentColumn); randomMove(); Mosaic.delay(5); } } // نهاية main static void fillWithRandomColors() { for (int row=0; row < ROWS; row++) { for (int column=0; column < COLUMNS; column++) { changeToRandomColor(row, column); } } } // نهاية fillWithRandomColors static void changeToRandomColor(int rowNum, int colNum) { // اختر قيم عشوائية تتراوح بين 0 و 255 // لقيم الألوان الثلاثة (الأحمر، و الأزرق، والأخضر) // بنظام الألوان RGB int red = (int)(256*Math.random()); int green = (int)(256*Math.random()); int blue = (int)(256*Math.random()); Mosaic.setColor(rowNum,colNum,red,green,blue); } // نهاية changeToRandomColor static void randomMove() { // اضبط القيمة عشوائيًا بحيث تتراوح من 0 وحتى 3 int directionNum; directionNum = (int)(4*Math.random()); switch (directionNum) { case 0: // تحرك للأعلى currentRow--; if (currentRow < 0) currentRow = ROWS - 1; break; case 1: // تحرك لليمين currentColumn++; if (currentColumn >= COLUMNS) currentColumn = 0; break; case 2: // تحرك للأسفل currentRow++; if (currentRow >= ROWS) currentRow = 0; break; case 3: // تحرك لليسار currentColumn--; if (currentColumn < 0) currentColumn = COLUMNS - 1; break; } } // نهاية randomMove } // نهاية الصنف RandomMosaicWalk2 التسمية وقواعد النطاق (scope rules) عندما نُصرِّح عن مُتَغيِّر ما (variable declaration)، يُخصِّص الحاسوب مساحة من الذاكرة لذلك المُتَغيِّر. بحيث تستطيع اِستخدَام اسم ذلك المُتَغيِّر ضِمْن جزء معين على الأقل من شيفرة البرنامج؛ بهدف الإشارة إلى تلك المساحة من الذاكرة أو إلى تلك البيانات المُخزَّنة بها. يُطلَق على ذلك الجزء من الشيفرة، والذي يَكُون فيه اسم المُتَغيِّر صالحًا للاِستخدَام، اسم نطاق المُتَغيِّر (scope of variable). بالمثل، يُمكِن الإشارة إلى نطاق كُلًا من أسماء البرامج الفرعية (subroutine) وأسماء المُعامِلات الصُّوريّة (formal parameter). لنبدأ بالأعضاء الساكنة من البرامج الفرعية، والتي تُعدّ قواعد نطاقها (scope rule) بسيطة نوعًا ما. يَمتد نطاق (scope) أي برنامج فرعي ساكن إلى كامل الشيفرة المصدرية للصَنْف (class) المُعرَّف بداخله، أيّ يُمكِن استدعاء ذلك البرنامج الفرعي من أيّ مكان داخل الصنف، بما في ذلك تلك الأماكن الواقعة قَبْل تعريف (definition) البرنامج الفرعي. بل أنه حتى من المُمكن لبرنامج فرعي معين استدعاء ذاته، وهو ما يُعرَف باسم التَعاود أو الاستدعاء الذاتي (recursion)، وهو أحد المواضيع المتقدمة نسبيًا، وسنتناوله عمومًا بالقسم ٩.١. وأخيرًا، إذا لم يَكُن البرنامج الفرعي خاصًا (private)، فتستطيع حتى الوصول إليه من خارج الصَنْف المُعرَّف بداخله بشَّرْط اِستخدَام اسمه الكامل. لننتقل الآن إلى الأعضاء الساكنة من المُتَغيِّرات، والتي تَملُك قواعد نطاق (scope rule) مشابهة بالإضافة إلى تعقيد واحد إضافي هو كالآتي. تستطيع عمومًا تعريف مُتَغيِّر محليّ (local variable) أو مُعامِل صُّوريّ (formal parameter) يَحمِل نفس اسم إحدى المُتَغيِّرات الأعضاء (member variable) ضِمْن الصَنْف، وفي تلك الحالة، يُعدّ المُتَغيِّر العضو مخفيًا ضِمْن نطاق المُتَغيِّر المحليّ أو المُعامِل الذي يَحمِل نفس الاسم. فعلى سبيل المثال، إذا كان لدينا الصَنْف Game كالتالي: public class Game { static int count; // متغير عضو static void playGame() { int count; // متغير محلي . . // بعض التعليمات لتعريف playGame() . } . . // المزيد من المتغيرات والبرامج الفرعية . } // نهاية الصنف Game يُشير الاسم count بالتَعْليمات المُؤلِّفة لمَتْن (body) البرنامج الفرعي playGame() إلى المُتَغيِّر المحليّ (local variable). أما ببقية الصنف Game، فإنه سيُشيِر إلى المُتَغيِّر العضو (member variable)، بالطبع إذا لم يُخفَى باستخدام مُتَغيِّر محليّ آخر أو مُعامِلات تَحمِل نفس الاسم count. مع ذلك، ما يزال بإمكانك الإشارة إلى المُتَغيِّر العضو count بواسطة اسمه الكامل Game.count، والذي يُستخدَم، في العادة، خارج الصَنْف الذي عُرِّف به العضو، ولكن ليس هناك قاعدة تَمنع اِستخدَامه داخل الصنف. لهذا يُمكِن اِستخدَام الاسم الكامل Game.count داخل البرنامج الفرعي playGame() للإشارة إلى المُتَغيِّر العضو بدلًا من المُتَغيِّر المحليّ. يُمكِننا الآن تلخيص قاعدة النطاق (scope rule) كالآتي: يَشمَل نطاق مُتَغيِّر عضو ساكن الصنف المُعرَّف بداخله بالكامل، وعندما يُصبِح الاسم البسيط (simple name) للمُتَغيِّر العضو مَخفيًا نتيجة تعريف مُتَغيِّر محليّ أو مُعامِل صُّوريّ يَحمِل نفس الاسم، يُصبِح من الضروري اِستخدَام الاسم الكامل على الصورة . للإشارة إلى المُتَغيِّر العضو. تُشبِه قواعد نطاق الأعضاء غير الساكنة (non-static) عمومًا تلك الخاصة بالأعضاء الساكنة، باستثناء أن الأولى لا يُمكِن اِستخدَامها بالبرامج الفرعية الساكنة (static subroutines)، كما سنرى لاحقًا. أخيرًا، يَتَكوَّن نطاق المُعامِل الصُّوريّ (formal parameter) لبرنامج فرعي معين من الكُتلَة (block) المُؤلِّفة لمَتْن البرنامج الفرعي (subroutine body). في المقابل، يَمتَد نطاق المُتَغيِّر المحليّ (local variable) بدايةً من تَعْليمَة التَّصْريح (declaration statement) المسئولة عن تعريف ذلك المُتَغيِّر وحتى نهاية الكُتلَة (block) التي حَدَثَ خلالها ذلك التَّصْريح. كما أشرنا بالأعلى، تستطيع التَّصْريح عن المُتَغيِّر المُتحكِّم بحَلْقة (loop control variable) for ضمن التعليمة ذاتها على الصورة for (int i=0; i < 10; i++)، ويُعدّ نطاق مثل هذا التَّصْريح (declaration) حالة خاصة: فهو صالح فقط ضِمْن تَعْليمَة for، ولا يَمتَد إلى بقية الكُتلَة المتضمنة للتَعْليمَة. لا يُسمَح عمومًا بإعادة تعريف (redefine) اسم المُعامِل الصُّوريّ أو المُتَغيِّر المحليّ ضِمْن نطاقه (scope)، حتى إذا كان ذلك داخل كُتلَة مُتداخِلة (nested block)، كالتالي: void badSub(int y) { int x; while (y > 0) { int x; // خطأ، لأن x مُعرَّفة بالفعل . . . } } في الواقع، تَسمَح بعض اللغات بذلك، بحيث يُخفِي التَّصْريح عن x ضِمْن حَلْقة while التَّصْريح الأصلى، ولكن لا تَسمَح الجافا بذلك، حيث يُصبِح اسم المُتَغيِّر متاحًا للاِستخدَام مرة آخرى فقط بعد انتهاء تَّنْفيذ الكُتلَة (block) المُصرَّح عن المُتَغيِّر ضِمْنها. اُنظر على سبيل المثال: void goodSub(int y) { while (y > 10) { int x; . . . // ينتهي نطاق x هنا } while (y > 0) { int x; // صالح، فتَّصريح x السابق انتهت صلاحيته . . . } } هل تَتَسبَّب أسماء المُتَغيِّرات المحليّة (local variable) بإخفاء أسماء البرامج الفرعية (subroutine names)؟ لا يُمكِن حُدوث ذلك لسبب قد يبدو مفاجئًا. ببساطة، لمّا كانت أسماء البرامج الفرعية دومًا مَتبوعة بزوج من الأقواس (parenthesis)، والتي يُسْتحسَن التفكير بها على أساس أنها جزء من اسم البرنامج الفرعي، كأن تقول البرنامج الفرعي main() وليس البرنامج الفرعي main، فإن الحاسوب عمومًا يُمكِنه دائمًا مَعرِفة ما إذا كان اسم معين يُشير إلى مُتَغيِّر أم إلى برنامج فرعي، لذا ليس ضروريًا أن تكون أسماء المُتَغيِّرات والبرامج الفرعية مختلفة من الأساس، حيث يُمكِنك ببساطة تعريف مُتَغيِّر اسمه count وبرنامج فرعي بنفس الاسم count ضِمْن نفس الصَنْف. كذلك لمّا كان الحاسوب قادرًا على مَعرِفة ما إذا كان اسم معين يُشير إلى اسم صَنْف أم لا وِفقًا لقواعد الصيغة (syntax)، فإنه حتى يُمكِن إعادة اِستخدَام أسماء الأصناف (classes) بهدف تسمية كلا من المُتَغيِّرات والبرامج الفرعية. اسم الصنف هو بالنهاية نوع، لذا يُمكِن اِستخدَام ذلك الاسم للتََّصْريح عن المُتَغيِّرات والمُعامِلات الصُّوريّة (formal parameters)، بالإضافة إلى تَخْصيص نوع القيمة المُعادة (return type) من دالة (function) معينة. يَعنِي ذلك أنك تستطيع التََّصْريح عن الدالة التالية ضِمْن صَنْف اسمه Insanity: static Insanity Insanity( Insanity Insanity ) { ... } يَستخدِم التََّصْريح -بالأعلى- الاسم Insanity ٤ مرات، تُشير الأولى إلى النوع المُعاد (return type) من الدالة، بينما تُشير الثانية إلى اسم تلك الدالة، والثالثة إلى نوع مُعاملها الصُّوريّ (formal parameter)، والرابعة إلى اسم ذلك المُعامِل. لكن تَذَكَّر! لا يُعدّ كل ما هو مُتاح فكرة جيدة بالضرورة. ترجمة -بتصرّف- للقسم Section 8: The Truth About Declarations من فصل Chapter 4: Programming in the Large I: Subroutines من كتاب Introduction to Programming Using Java.1 نقطة
-
كل تَطوُّر يَحدُث بواجهات المُستخدِم، يُقابله تَعقيد أكبر ينبغي على المبرمج التَعامُل معه، ففي حين تستطيع كتابة واجهة برنامج طرفية (console user interface) بواسطة عدد قليل من البرامج الفرعية (subroutines) البسيطة التي تقرأ ما يَكْتُبه المُستخدِم، وتَطبَع خَرْج البرنامج إلى الطرفية، فإن تطوير واجهات المُستخدِم الرسومية (graphical user interface) العصرية هو أمر أكثر تعقيدًا بمراحل عدة، خاصة مع كل هذا الكم الهائل من النوافذ، والأزرار، وشرائط التمرير، والقوائم، وصناديق الإِدْخَال النصية، وغيره، والتي أينعم تَمنَح المُستخدِم تجربة سهلة ومريحة، ولكنها في ذات الوقت تُجبر المبرمج على التَعامُل مع كمية هائلة من الاحتمالات والتعقيدات المتزايدة، والتي تَكُون بهيئة عدد ضخم من البرامج الفرعية المُخصَّصة فقط لأغراض إدارة واجهة المُستخدِم (user interface) ناهيك عن البرامج الفرعية الآخرى المُخصَّصة لغَيْر ذاك الغرض. صناديق الأدوات (toolboxes) اعتاد مبرمجي حاسوب ماكنتوش الأصلي (Macintosh) على التعامل مع "صندوق أدوات ماكنتوش (Macintosh Toolbox)"، والذي يَتكوَّن من أكثر من ألف برنامج فرعي (subroutines) مختلف؛ حيث تَتَوفَّر برامج لتَّنْفيذ جميع العمليات التي يُتوقَّع من الحاسوب القيام بها، فمثلًا، تَتَوفَّر تلك البرامج المُتعلقة بواجهة المُستخدِم (user interface) مثل فَتْح النوافذ (windows) وإِغلاقها، ورَسْم كُلًا من الأشكال الهندسية (geometric) والنصوص على تلك النوافذ، وإضافة الأزرار إليها، والاستجابة إلى ضغطات الفأرة على تلك النوافذ، وإضافة القوائم (menus)، والاستجابة لما يَختاره المُستخدِم منها. إلى جانب برامج واجهة المُستخدِم، تَتَوفَّر أيضًا برامج لفَتْح الملفات، وقراءة البيانات منها، وكذلك للاتصال الشبكي، ولإرسال الخَرْج إلى الطابعة، ولمُعالجة الاتصال بين البرامج. في المُقابل، يُوفِّر مايكروسوفت ويندوز (Microsoft Windows) مجموعة آخرى من البرامج الفرعية (subroutines) للمبرمجين، والتي تَختلِف نوعًا ما عن تلك المُستخدَمة بماكنتوش (Macintosh). علاوة على ذلك، يُوفِّر لينكس (Linux) للمبرمجين أكثر من صندوق أدوات لبرمجة واجهات المُستخدِم الرسومية (GUI toolboxes) يُمكِنهم الاختيار بينها. أيّ مشروع برمجي هو بالنهاية خليط من كُلًا من الابتكار وإعادة الاِستخدَام. يبدأ المبرمج بمجموعة الأدوات البسيطة المَبْنِيَّة باللغة نفسها، كالمتغيرات، وتَعْليمَات الإِسْناد (assignment statements)، وتَعْليمَات التَفْرِيع if، وحَلْقات التَكْرار (loops). إلى جانب ذلك، فإنه قد يَستعِين بصناديق أدوات (toolboxes)، مَكْتوبة من قِبَل مُطوِّرين آخرين، والتي تَتضمَّن مجموعة من البرامج (routines) لتَّنْفيذ مَهَامّ معينة يَحتاجها المبرمج. إذا كانت تلك الأدوات مُصمَّمة تَصْمِيمًا جيدًا، فإنها تَكُون أَشْبه بصندوق أسود (black boxes)، لا يحتاج المبرمج أكثر من مُجرَّد استدعائها، دون مَعرِفة أية تفاصيل أو خطوات تُنفِّذها تلك الأداة لإنجاز المُهِمّة المُسنَدة إليها. كُل ما سبق يصُنَّف ضِمْن ذلك الجزء الخاص بإعادة الاِستخدَام، أما الجزء الآخر المُتَعلِّق بالابتكار، فيَتكوَّن من توظيف كل تلك الأدوات السابق ذِكرَها ضِمْن مشروع معين بهدف حل مشكلة معينة كمُعالجة النصوص، أو الاحتفاظ بالحسابات البنكية، أو مُعالجة الصور المُلتقَطة بواسطة مسبار فضاء، أو ألعاب الكمبيوتر، أو تَصفُّح الإنترنت، ..إلخ. يُطلَق على ذلك برمجة التطبيقات (applications programming). صندوق الأدوات البرمجي (software toolbox) هو أَشْبه ما يَكُون بصندوق أسود، لابُدّ أن يَكُون مَصحُوبًا بواجهة (interface)، والتي تَتَكوَّن من توصيف لجميع البرامج (routines) المُعرَّفة بداخله، أيّ ذِكْر مُعامِلات (parameters) كل برنامج، والغرض منه. تُكوِّن تلك التوصيفات ما يُعرَف باسم واجهة برمجة التطبيقات (Application Programming Interface)، وتُختصَر إلى API، فمثلًا، تَحتوِي واجهة برمجة تطبيقات ماكنتوش (Macintosh API) على تَوصِيف لجميع البرامج (routines) المُتاحة "بصندوق أدوات ماكنتوش (Macintosh Toolbox)". عادةً ما تُصدر الشركات المُصنعة للأجهزة العتادية (hardware device) -مثل بطاقات الشبكة (network cards) المسئولة عن توصيل الحاسوب بشبكة معينة- واجهة برمجة تطبيقات (API) خاصة بذلك الجهاز، تَحتوِي على قائمة بالبرامج (routines) التي يُمكِن للمبرمجين استدعائها؛ للاتصال مع الجهاز والتَحكُّم به. علاوة على ذلك، يُوفِّر العلماء المُساهمين بكتابة البرامج المسئولة عن حِسَاب بعض العمليات المُعقدة نوعًا ما -مثل حلّ المعادلات التفاضلية (differential equations)- واجهة برمجة تطبيقات (API)، تَسمَح للآخرين باستدعاء تلك البرامج (routines) دون الحاجة إلى فهم تفاصيل تلك العمليات. لغة الجافا مُدعَّمة بواجهة برمجة التطبيقات القياسية (standard API)، والتي تَعرَّضنا لأجزاء منها بالفعل، مثل البرنامج الفرعي الرياضي Math.sqrt()، والصَنْف String، وما يَحتوِيه من برامج (routines)، بالإضافة إلى برامج طباعة الخَرْج System.out.print(). تَتضمَّن أيضًا تلك الواجهة برامج (routines) لبرمجة واجهات المُستخدِم الرسومية (graphical user interfaces)، وللاتصالات الشبكية (network communication)، ولقراءة الملفات وكتابتها، وغيرها. يَظُنّ البعض أن تلك البرامج (routines) هي جزء من لغة الجافا ذاتها، ولكنها في الواقع مُجرَّد برامج فرعية (subroutines) قد كُتبت وأُتيحت للاِستخدَام ببرامج (programs) الجافا. ينبغي لأيّ واجهة برمجة تطبيقات جافا (Java API) العَمَل على جميع المنصات، وعندها يُعدّ برنامج (program) الجافا المَكْتوب وفقًا لتلك الواجهة مُستقلًا عن أيّ منصة (platform-independent)، أيّ أنه من المُمكِن تَشْغِيل نفس البرنامج (program) على منصات (platform) مُتعدِّدة، مثل ويندوز (Windows)، وماك (Mac OS)، ولينكس (Linux) وغيرها. ومع ذلك، لاحِظ أن الواجهة (interface) ذاتها هي التي تُعدّ مُستقلة عن المنصات (platform-independent)، أما تَّنْفيذ (implementation) تلك الواجهة فإنه قد يختلف من منصة لآخرى. فيما يتعلق بواجهة برمجة التطبيقات القياسية (standard API)، يَتضمَّن نظام الجافا (Java system) بأي حاسوب عمومًا تَّنْفيذًا (implementations) لجميع البرامج الموجودة بتلك الواجهة. عندما يَستدعِي برنامج جافا (Java program) واحدة من تلك البرامج القياسية (standard routines) ضِمْن الواجهة القياسية، فإن مُفسِّر الجافا (Java interpreter) -عند تَّنْفيذه للبرنامج (program)- يَسحَب تَّنْفيذ البرنامج المُستدعَى (routine implementation) المُتناسب مع المنصة (platform) الحالية، ثم يُنفِّذه. يَعنِي ذلك أنه بمُجرَّد تَعلُّمك لواجهة برمجة تطبيقات (API) واحدة، سيُصبِح بإمكانك استهداف مجموعة واسعة من المنصات، وهو ما يُعدّ ميزة قوية جدًا. حزم الجافا القياسية (standard packages) تُكتَب البرامج (routines) بواجهة برمجة التطبيقات القياسية (standard API) داخل أصناف (classes) كما هو الحال مع أي برنامج فرعي (subroutine). بالإضافة إلى ذلك، فإنه من المُمكِن تَجْميع الأصناف ضِمْن حزم (packages)، والتي تَعرَّضنا لها باختصار بالقسم الفرعي ٢.٦.٧؛ بهدف تنظيمها على نطاق أوسع (large-scale). يُمكِن أيضًا تَضْمِين الحزم (packages) داخل حزم آخرى لتحقيق مُستوى أعلى من التَجْميع. في الواقع، واجهة برمجة تطبيقات جافا القياسية (standard Java API) مُنفَّذة (implement) بالكامل ضِمْن عدة حزم، فمثلًا، تَحتوِي حزمة java على عدة حزم آخرى غَيْر مُتَعلِّقة بواجهة المُستخدِم الرسومية (non-GUI)، بالإضافة إلى احتوائها على أصناف AWT المُتَعَلِّقة بتلك الواجهات الرسومية. حزمة javax هي مثال آخر، وتَحتوِي على أصناف كثيرة، من ضِمْنها تلك الأصناف التي تَستخدِمها واجهة المُستخدِم الرسومية (GUI) Swing. كذلك حزمة javafx والتي تَحتوِي على واجهة برمجة تطبيقات جافا إف إكس (JavaFX API) التي يَستخدِمها الكتاب لبرمجة واجهات المُستخدِم الرسومية (GUI). قد تَحتوِي أي حزمة (package) على أصناف أو حزم آخرى، وتُسمَى تلك الأخيرة باسم الحزم الفرعية (sub-package). تَتضمَّن كُلًا من الحزمتين java و javafx على حزم فرعية (sub-packages). فمثلًا، تَحتوِي الحزمة الفرعية util على تشكيلة من الأصناف، بما في ذلك الصَنْف Scanner الذي ناقشناه بالقسم الفرعي ٢.٤.٦، وهي في الواقع مُتضمَّنة داخل حزمة java، ولذا يَكُون اسمها الكامل هو java.util. مثال آخر هو الحزمة الفرعية java.io، والتي تُسهِل من عمليات الخَرْج والدَخْل (input/output). كذلك الحزمة الفرعية java.net، والتي تتعامل مع الاتصالات الشبكية. أخيرًا الحزمة الفرعية الأكثر بساطة java.lang، والتي تَحتوِي على الأصناف الأساسية مثل String و Math و Integer و Double. اُنظر الصورة التالية والتي تَتضمَّن تمثيلًا رسوميًا (graphical representation) لمُستويات التَدَاخُل (nesting levels) بحزمة java، بما في ذلك حزمها الفرعية (sub-packages)، والأصناف الموجودة ضِمْن تلك الحزم الفرعية، بالإضافة إلى البرامج الفرعية (subroutines) الموجودة ضِمْن تلك الأصناف. لاحِظ أن هذا التمثيل الرسومي غَيْر كامل، فهو يَعرِض فقط عددًا قليلًا جدًا من العناصر الكثيرة الموجودة بكل عنصر. ] بالمثل، تَحتوِي حزمة javafx على الحزمة الفرعية javafx.scene، والتي تَحتوِي بدورها على كُلًا من الحزمتين الفرعيتين javafx.scene.control و javafx.scene.paint. تَحتوِي أولاهما على أصناف (classes) لتمثيل مُكوِّنات واجهة المُستخدِم الرسومية (GUI components)، كالأزرار (buttons)، وصناديق الإِدْخال (input boxes)، بينما تَحتوِي الآخرى على الصَنْف Color، وأصناف آخرى لأغراض مَلْئ الأشكال (filling) وتَحْدِيد حوافها (stroking). تَتضمَّن واجهة برمجة تطبيقات جافا القياسية (standard Java API) آلافًا من الأصناف مُجمَّعة ضِمْن مئات من الحزم. الكثير من تلك الأصناف هو، في الواقع، مُتخصِّص للغاية أو غَيْر مَعْروف، لذا لا داعي للإلمام بكامل واجهة برمجة تطبيقات جافا (Java API)، ولا حتى غالبيتها، فحتى خبراء المبرمجين ليسوا على دِرَايَة بكامل الواجهة. ستُواجه عشرات الأصناف (classes) أثناء دراستك لهذا الكتاب، وستَجِدْ أنهم كافيين تمامًا لكتابة تشكيلة واسعة من البرامج (programs). مع ذلك، يُمكِنك تَصفُّح توثيق واجهة برمجة التطبيقات (API) للإصدار ٨ من الجافا بالإضافة إلى توثيق واجهة برمجة تطبيقات جافا إف إكس (JavaFX) للإطلاع على ما هو مُتاح عمومًا. يُناقِش القسم الفرعي "الوحدات (modules)" بالأسفل بعضًا من التَغْيِيرات التي طرأت بالإصدار ٩ من الجافا، كما ستَجِدْ روابط تَوْثيق (documentation) الإصدار ١١ من الجافا. لكن لاحِظ أن تَوْثيق الإصدار ٨ يُعدّ أكثر سهولة في الاِستخدَام فيما يَتعلَّق بدراسة هذا الكتاب. استخدام الأصناف ضمن الحزم تَتضمَّن الحزمة javafx.scene.paint الصَنْف Color، لذا فإن الاسم الكامل للصنف هو javafx.scene.paint.Color. ذلك الصَنْف، وكأي صنف، هو بالنهاية نوع، أيّ أنك تستطيع اِستخدَامه للتَّصْريح (declare) عن كلًا من المُتَغيِّرات، والمُعامِلات (parameters)، وكذلك لتَخْصيص نوع القيمة المعادة (return type) من دالة (function). إذا أردت اِستخدَام ذلك الصَنْف ضِمْن أحد البرامج (program) التي تقوم بكتابتها، فإن أحد الطرائق للقيام بذلك هو استخدام الاسم الكامل للصَنْف كاسم للنوع. مثلًا، إذا كنت تريد التَّصْريح عن مُتَغيِّر اسمه rectColor من النوع Color، تستطيع كتابة التالي: javafx.scene.paint.Color rectColor; تُمثِل الشيفرة بالأعلى مُجرَّد تَّصْريح (declaration) عن مُتَغيِّر عادي على الصياغة ;. مع ذلك، فإن اِستخدَام الاسم الكامل للصَنْف هو حتمًا أمر مُتعب، وفي الواقع، نادرًا ما ستَجِدْ تلك الأسماء الكاملة مُستخدَمة بأيّ برنامج (program)؛ حيث تَسمَح الجافا بتَجَنُّب اِستخدَام الاسم الكامل للصَنْف، وفي المقابل، ستحتاج إلى اِستيراد (importing) ذلك الصَنْف أولًا. يُمكِنك استيراد الصَنْف بإضافة السَطْر التالي إلى بداية ملف الشيفرة المصدرية (source code): import javafx.scene.paint.Color; بذلك، تستطيع ببقية ذلك الملف كتابة الاسم البسيط (simple name) للصَنْف، أيّ Color، بدلًا من الاسم الكامل javafx.scene.paint.Color. لابُدّ أن يُكتَب سَطْر الاستيراد import ببداية الملف (بعد تَعْليمَة package في حالة وجودها)، وبحيث لا يَقَع سَطْر الاستيراد import ضِمْن أيّ صَنْف (class) مُعرَّف داخل الملف. يُسمِى البعض سَطْر الاستيراد import -بالأعلى- أحيانًا باسم التَعْليمَة (statement)، ولكن من الأنسب تَسميته بالمُوجِّه (directive) import؛ لأنه ليس تَعْليمَة بالمعنى المُعتاد. والآن، سَمَح لك المُوجِّه import javafx.scene.paint.Color بكتابة التالي للتَّصْريح عن مُتَغيِّر: Color rectColor; يَقْتصِر دور المُوجِّه (directive) import على السماح باِستخدَام الأسماء البسيطة للأصناف بدلًا من أسمائها الكاملة .، أي أنه لا يَستوِرد أي شيء فعليّ، فأنت ما زلت تستطيع الوصول إلى الصَنْف (class) بدون اِستخدَام ذلك المُوجِّه، فقط ستحتاج إلى تعيين اسم الصَنْف كاملًا. تَتَوفَّر طريقة مُختصرة لاستيراد (importing) جميع الأصناف الموجودة ضِمْن حزمة معينة. على سبيل المثال، اِستخدِم مُوجِّه import التالي بهدف استيراد جميع الأصناف (classes) الموجودة بحزمة java.util: import java.util.*; لاحِظ أنه في حين يَتطابَق محرف البدل (wildcard) * مع جميع الأصناف الموجودة ضِمْن حزمة معينة، فإنه لا يَتطابَق مع حزمها الفرعية (sub-packages)، أيّ أنك لا تستطيع استيراد جميع الحزم الفرعية (sub-packages) الموجودة ضِمْن حزمة javafx بمُجرَّد كتابة import javafx.*. لمّا كان اِستخدَام محرف البدل (wildcard) * بتَعْليمَة import يُتيح عددًا كبيرًا من أسماء الأصناف التي على الأرجح لن تُستخدَم، بل والتي ربما لا يَعَلم المبرمج عنها شيئًا، فإن بعض المبرمجين يُفضِّلون استيراد كل صَنْف سيَستخدِمونه فعليًا استيرادًا صريحًا وبصورة مُنفصلة. يَنصَح الكاتب باِستخدَام محرف البدل (wildcard) فقط مع الحزم (packages) الأكثر صلة بالتطبيق؛ بهدف استيراد جميع الأصناف (classes) الموجودة بها، أما في حالة اِستخدَام صَنْف واحد فقط أو اثنين من حزمة معينة، فلربما عندها من الأفضل اِستخدَام استيرادات (imports) فردية. على سبيل المثال، قد يَحتوِي برنامج يتعامل بصورة أساسية مع الشبكات على المُوجِّه import java.net.*;، بينما قد يَحتوِي برنامج آخر يقرأ الملفات ويكتبها على المُوجِّه import java.io.*;. لكن لاحِظ أنه في حالة بدأت باستيراد الكثير من الحزم بتلك الطريقة، فلابُدّ من الانتباه لأمر هام. قد تَحتوِي حزمتان مختلفتان على صَنْفين (classes) يَحمِل كلاهما نفس الاسم، فمثلًا تَحتوِي كُلًا من الحزمتين java.awt و java.util على صَنْف اسمه List. الآن، إذا استوردت كُلًا من java.awt.* و java.util.*، فسيُصبِح الاسم البسيط للصَنْف List مُبهمًا، وبالتالي، إذا حاولت التَّصْريح (declare) عن مُتَغيِّر من النوع List، ستَحصُل على رسالة خطأ من المُصرِّف (compiler) بشأن وجود اسم صَنْف مُبْهَم. مع ذلك، ما زلت تستطيع اِستخدَام كِلا الصنفين بالبرنامج (program)، إما باِستخدَام الاسم الكامل للصَنْف مثل java.awt.List و java.util.List، أو باِستخدَام المُوجِّه import لاستيراد الأصناف المُفردة التي تحتاجها بدلًا من استيراد الحزم (packages) بالكامل. تُعدّ الحزمة java.lang أحد أهم الحزم الأساسية؛ لاحتوائها على بعض الأصناف الرئيسية، ولذلك تُستورَد جميع الأصناف الموجودة بتلك الحزمة أتوماتيكيًا بأيّ برنامج (program)، أيّ كما لو كان كل برنامج يبدأ بالتَعْليمَة import java.lang.*;. وهو في الواقع ما قد مَكَّنا سابقًا من اِستخدَام اسم الصَنْف String بدلًا من java.lang.String، واِستخدَام Math.sqrt() بدلًا من java.lang.Math.sqrt(). ومع ذلك، يُمكِنك أيضًا اِستخدَام الصياغة الأطول من أسماء تلك الأصناف. تُستخدَم تَعْليمَة package لإنشاء حزم (packages) جديدة، والتي قد تَتضمَّن مجموعة من الأصناف (classes). كل ما عليك القيام به هو كتابة تلك التَعْليمَة ببداية ملفات الشيفرة المصدرية (source code) التي تَحتوِي على تعريف الأصناف المطلوب تَضْمِينها بتلك الحزمة. مثلًا، إذا أردت إنشاء حزمة اسمها utilities، اِستخدِم السطر التالي: package utilities; لابُدّ أن تُكْتَب تَعْليمَة package ببداية الملف، أيّ حتى قَبْل أيّ مُوجِّه import قد يَكُون موجودًا بذلك الملف. بالإضافة إلى ذلك، لابُدّ أن يوجد ذلك الملف بمجلد يَحمِل نفس اسم الحزمة، أي utilities بهذا المثال. وبالمثل، لابُدّ أن توجد ملفات الأصناف ضِمْن الحزم الفرعية (sub-package) بمجلدات فرعية. على سبيل المثال، ينبغي لملفات الأصناف الموجودة بحزمة اسمها utilities.net أن تكون موجودة بمجلد اسمه net الموجود بدوره بمجلد آخر اسمه utilities. يستطيع أي صَنْف داخل حزمة (package) معينة الوصول أتوماتيكيًا إلى جميع الأصناف (classes) الآخرى الموجودة بنفس الحزمة. بتعبير آخر، لا يحتاج صَنْف معين أن يَستورِد (import) الأصناف من نفس الحزمة (package) المُعرَّف بها. عادة ما يَلجأ المبرمجون لإنشاء حزم جديدة وذلك إِما بالمشروعات التي تُعرِّف عددًا كبيرًا من الأصناف؛ بهدف تنظيمها، أو عند إِنشائهم لصناديق أدوات (toolboxes)؛ لتوفير واجهة برمجة تطبيقات (APIs) لبعض الوظائف والميزات غير المُتوفِّرة بواجهة برمجة تطبيقات جافا القياسية (standard Java API). في الواقع، يَحظَى المبرمجون "من صَانِعي الأدوات" عادة بهَيْبة واحترام أكبر من مُبرمجي التطبيقات الذين يَستخدِمون تلك الأدوات. لاحظ أن غالبية الأصناف المَكْتوبة لهذا الكتاب غَيْر مُضمَّنة بأي حزمة، مع عدة استثناءات قليلة مثل الصَنْف TextIO بالحزمة textio. لأغراض هذا الكتاب، فإنك ستحتاج أن تدرك ماهية الحزم (packages)؛ حتى تَتَمكَّن من استيراد الصَنْف TextIO، وكذلك الأصناف الموجودة ضِمْن الحزم القياسية (standard packages). لاحِظ أن الحزم القياسية هي دائمًا مُتوفِّرة بجميع البرامج (programs) التي تَكتُبها، قد تَتَساءل، أين توجد تلك الأصناف القياسية (standard classes) بصورة فعليّة؟ يَعتمِد ذلك على إصدار الجافا المُستخدَم إلى حد كبير. مثلًا، بالإصدار ٨ من الجافا، فإنها تَكُون مُخزَّنة بملفات جافا أَرْشيفيّة (jar files/Java archive) تقع بالمجلد الفرعي lib الموجود بمجلد التثبيت الخاص ببيئة تَّنْفيذ الجافا (Java Runtime Environment). إن ملفات جافا الأَرْشيفيّة (jar file/Java archive) هي ببساطة ملفات بامتداد .jar قد تَحتوِي على عدة أصناف. توجد غالبية الأصناف المُستخدَمة بالإصدار ٨ من الجافا بملف جافا أرشيفي (jar file) اسمه rt.jar. طرأت بعض التَغْيِيرات بالإصدار ٩ من الجافا، وهو ما سنُناقشه بالقسم الفرعي التالي. لاحِظ أن أيّ صَنْف بالنهاية لابُدّ وأن يقع ضِمْن حزمة (package)، حتى في حالة عدم تَحْدِيد الحزمة التي يُفْترَض أن يقع بها الصنف صراحةً، وفي تلك الحالات، يُوضَع الصَنْف تلقائيًا فيما يُعرَف باسم الحزمة الافتراضية (default package)، والتي ليس لها اسم. تقع تقريبًا كل الأمثلة التي ستراها بهذا الكتاب بالحزمة الافتراضية. الوحدات (modules) تُدعِّم الجافا منذ الإصدار ٩ ما يُعرَف باسم الوحدات (modules)، وهو ما تَسَبَّب بحُدوث بعض التَغْيِيرات على بنيتها ذات النطاقات الواسعة (large-scale structure). تُوفِّر الوحدات (modules) عمومًا مستوًى آخرًا من التَجْميع؛ حيث تَتضمَّن الوحدات مجموعة من الحزم، التي تَحتوِي على أصناف، والتي بدورها تَحتوِي على مُتَغيِّرات وتوابع (methods). ليس ضروريًا أن تقع الحزمة ضِمْن وحدة (module) حتى تُصبِح قابلة للاِستخدَام، ومع ذلك، فإن جميع الأصناف القياسية (standard classes) بكُلًا من جافا (Java) وجافا إف إكس (JavaFX) قد ضُمّنت داخل وحدات (modules). وَفَّرت الجافا الوحدات (modules) لعدة أسباب: أولًا، لتَحسِّين التَحكُّم بالوصول (access control)، وهو في الواقع أحد أهم الأسباب الرئيسية. قَبْل الوحدات، كان بإِمكانك اِستخدَام الأصناف العامة، أيّ تلك المُصرَّح عنها باِستخدَام المُبدِّل public، بأيّ مكان وداخل أيّ صَنْف ضِمْن أيّ حزمة، وبالمثل مُتَغيِّراته وتوابعه المُصرَّح عنها باِستخدَام المُبدِّل public. في المقابل، يَعنِي اِستخدَام المُبدِّل العام public مع صَنْف، مُعرَّف ضِمْن وحدة (module)، كَوْن ذلك الصَنْف عامًا public ضِمْن الوحدة (module) المُعرَّف بداخلها فقط. ومع ذلك، ما يزال بإمكان الوحدات تصدير (export) أي حزمة بشكل صريح، وبناءً عليه، ستُصبِح الأصناف العامة، أيّ تلك المُعرَّفة باِستخدَام المُبدِّل public، ضمن تلك الحزمة قابلة للوصول (accessible) مرة آخرى من أي مكان، بما في ذلك الوحدات (modules) الآخرى، وكذلك الأصناف التي ليست جزءًا من أي وحدة. يمكن حتى تَخْصيص الوحدات (modules) المطلوب تصدير الحزمة إليها، وهو ما يُوفِّر تَحكُّمًا بالوصول (access control) أكثر دقة. يَعنِي كل ما سبق أنه يُمكِننا الآن إنشاء حزم هي خاصة (private) بالأساس، أيّ أنها غَيْر مَرئية (invisible) من خارج الوحدة، ولكنها في نفس الوقت تُوفِّر خدمات (services) للحزم الآخرى داخل نفس الوحدة. يُمكِننا بذلك عدّ الوحدة نوعًا آخرًا من الصندوق الأسود (black box)، تَكُون فيه الحزم غَيْر المُصدَّرة (non-exported) جزءًا من تَّنْفيذه (implementation) الخفي. تُعدّ التركيبية (modularity) على هذا المُستوى الواسع من النطاق مهمة بحقّ، وبالأخص للتطبيقات ذات النطاقات الواسعة (large-scale applications). الدافع الآخر لتَوْفِير الوحدات (modules) هو الحجم الكلي لبيئة تَّنْفيذ الجافا القياسية (Java Runtime Environment)، وتُختصر إلى JRE. تَتضمَّن تلك البيئة جميع الأصناف القياسية (standard classes) على الرغم من أن التطبيقات (application) تَستخدِم عادةً جزءًا صغيرًا منها. تَسمَح التركيبية (Modularization) بإنشاء بيئات تَّنْفيذ جافا مُخصَّصة (custom JREs) أصغر؛ بحيث تحتوي فقط على الوحدات التي يحتاجها التطبيق. تَتضمَّن عُدة تطوير الجافا (Java Development Kit)، وتُختصر إلى JDK، الأمر jlink المُستخدَم لإنشاء بيئات تَّنْفيذ مُخصَّصة (custom runtimes)، والتي عادةً ما تَتضمَّن وحدات (modules) التطبيق ذاته بالإضافة إلى الوحدات القياسية (standard modules) المطلوبة لتَشْغِيل ذلك التطبيق (application). بعد ذلك، تُوزَّع (distribute) بيئات التَّنْفيذ تلك كتطبيق قائم بذاته (standalone)، والذي يُمكِن تَشْغِيله حتى بتلك الحواسيب التي لم تُثبَّت عليها عُدة تطوير الجافا (JDK). ولكن لاحِظ أنك ستحتاج إلى إنشاء إصدارات مختلفة من بيئة التَّنْفيذ المُخصَّصة (custom runtime) للمنصات المختلفة مثل الويندوز (Windows) والماك (Mac OS) ولينكس (Linux)، كما هو الحال مع عُدة تطوير الجافا (JDK) ذاتها. بالإضافة إلى ذلك، لن تُطبَق تحديثات الأمان (security updates) الصادرة لعُدة تطوير الجافا (JDK) أتوماتيكيًا على بيئات التَّنْفيذ المُخصَّصة (custom runtime)، ولذلك تقع مسئولية تحديثها على مُطوِّر التطبيق. عمومًا يُعدّ ذلك ميزة مفيدة جدًا للتطبيقات الكبيرة. منذ الإصدار ٩ من الجافا، تُخزَّن ملفات أصناف الوحدات القياسية (standard modules) المُصرَّفة داخل الملف modules، والموجود بالمجلد الفرعي lib الموجود بدوره بالمجلد الرئيسي لعُدة تطوير الجافا (JDK). صيغة ذلك الملف هي jimage، يُمكِنك التعامل معها باِستخدَام أداة سطر الأوامر jimage. في الواقع، عندما تُستخدَم أداة jlink لإنشاء بيئة تَّنْفيذ مُخصَّصة (custom runtime)، فإن جزءًا مما تقوم به هو إِنشاء ملف modules مُخصَّص يَحتوِي فقط على الوحدات (modules) المطلوبة لبيئة التَّنْفيذ (runtime) تلك. بفَحْص مجلد الإصدار ١٢ من عُدة تطوير الجافا (JDK) بحاسوب لينكس (Linux) الخاص بالكاتب، تَبيَّن أن الملف modules يَتضمَّن حوالي ٣٠١٩٩ صَنْف، ضِمْن ١٠٠٠ حزمة، ضِمْن ٧٠ وحدة، كما وصل حجمه إلى حوالي ١٣٠ ميجا بايت. يَحتوِي المجلد الرئيسي لعُدة تطوير الجافا (JDK) أيضًا على المجلد الفرعي jmods، والذي يَتضمَّن تلك الوحدات (modules) ولكن بصيغة آخرى، وعمومًا هو ليس مطلوبًا لتَصْرِيف البرامج وتَشْغِيلها، وغالبًا يَقْتصِر اِستخدَامه على الأداة jlink على حَدْ عِلم الكاتب. تَتضمَّن عُدة تطوير الجافا (JDK) مجموعة من الوحدات، من بينها الوحدتين java.base و java.desktop. تَحتوِي أولاهما على الحزم الأساسية مثل java.lang و java.util، بينما تَتضمَّن الثانية حزم (packages) خاصة بـ"أدوات تَحكُّم واجهة المُستخدَم الرسومية Swing (Swing GUI toolkit)". في المقابل، تَتضمَّن منصة جافا إف إكس (JavaFX) كُلًا من الوحدات javafx.base و javafx.control و javafx.graphics بالإضافة إلى وحدات آخرى غير شائعة الاِستخدَام عمومًا. يُقسَّم تَوْثيق واجهة برمجة التطبيقات (API) للإصدارات التركيبية (modular) من الجافا إلى وحدات (modules)، مُقسَّمة بدورها إلى حزم (packages)، وأخيرًا إلى أصناف (classes)، وهو ما قد يَجعَل التَوْثيق (documentation) أصعب قليلًا في التَصفُّح بالموازنة مع الإصدارات الأقدم. مع ذلك تَتوفَّر مِيزَة بحث فعالة بالموقع الالكتروني للتَوْثيق. يُمكِنك تَصفُّح توثيق الإصدار ١١ من الجافا بالإضافة إلى توثيق الإصدار ١١ من جافا إف إكس (JavaFX). ليس ضروريًا أن يَقَع الصَنْف ضِمْن وحدة (module)، وفي تلك الحالة، فإنه ما يزال يستطيع اِستخدَام الحزم من الوحدات الاخرى، بشَّرْط أن تَكُون تلك الحزم قد صُدّرت (exported) بالوحدات المُعرَّفة داخلها. يستطيع المبرمج عمومًا اِستخدَام الأصناف الموجودة بعُدة تطوير الجافا (JDK) دون الحاجة للتفكير نهائيًا بالوحدات أو حتى مَعرِفة وجودها. يَنطبِق ذلك على برامج سطر الأوامر (command-line programs) بهذا الكتاب، أما برامج واجهة المُستخدِم الرسومية (GUI programs) التي تَستخدِم منصة جافا إف إكس (JavaFX)، فالأمور تختلف قليلًا منذ الإصدار ١١ من الجافا؛ وذلك لأن تلك المنصة قد حُذفَت من عُدة تطوير الجافا (JDK)، وأصبحت تُوزَّع بصورة مستقلة كمجموعة من الوحدات. لذلك، عند تَصْرِيف برنامج جافا إف إكس (JavaFX)، أو تَشْغِيله، ستحتاج، كما رأينا بالقسم ٢.٦، إلى تَخْصيص مسار الوحدة (module path)، والذي لابُدّ أن يَحتوِي على وحدات تلك المنصة، كما ستحتاج إلى تمرير قيمة للخيار --add-modules. مُرِّرت ALL-MODULE-PATH كقيمة للخيار --add-modules بالقسم ٢.٦؛ للسماح للبرنامج بالوصول إلى أي وحدات (modules) موجودة بمسار الوحدة (module path) المُمرَّر. بدلًا من ذلك، قد تُمرِّر قائمة بأسماء الوحدات المُستخدَمة فعليًا بالبرنامج فقط. تنبيه: لا يُغطِى هذا الكتاب موضوع الوحدات (modules) بصورة أكبر من مُجرَّد اِستخدَامها مع منصة جافا إف إكس (JavaFX)، بالإضافة إلى المعلومات الأساسية بهذا القسم. التوثيق بأداة Javadoc ينبغي عمومًا كتابة تَوْثيق (documentation) جيد لأيّ واجهة برمجة تطبيقات (API)؛ وذلك حتى يَتَمكَّن المبرمجين من اِستخدَامها بصورة فعالة. يَشيِع اِستخدَام نظام Javadoc لتَوْثيق غالبية واجهات برمجة تطبيقات جافا (Java APIs)، فمثلًا، يُستخدَم هذا النظام لتجهيز تَوْثيق حزم الجافا القياسية (standard packages)، كما يَنشُر غالبية المبرمجين تقريبًا تَوْثيقًا باِستخدَام نفس ذلك النظام لأي صندوق أدوات (toolbox) يُطوروه بالجافا. يُجهَز تَوْثيق Javadoc بالاعتماد على مجموعة من التعليقات (comments) الخاصة، والتي تُكتَب بملفات الشيفرة المصدرية. كما تَعَلم، تُكتَب أحد أنواع تعليقات الجافا ضِمْن الترميزين /* و */. بالمثل، تُكتَب تعليقات Javadoc بنفس الصياغة لكنها تبدأ بالترميز /** بدلًا من الترميز /*. لقد تَعرَّضت بالفعل لتعليقات مَكْتوبة بتلك الصياغة بكثير من الأمثلة بهذا الكتاب. ينبغي أن تُوْضَع تعليقات Javadoc قَبْل البرنامج الفرعي المَعنِي بالتعليق مباشرة. لاحظ ضرورة اتباع تلك القاعدة عمومًا وبَغْض النظر عن العنصر المَعنِي بالتَوْثيق. تُستخدَم تعليقات Javadoc عمومًا مع البرامج الفرعية (subroutines)، والمُتَغيِّرات الأعضاء (member variables)، والأصناف (classes)، وفي جميع تلك الحالات، لابُدّ دائمًا أن يَسبِق تعليق Javadoc العنصر المَعنِي بالتعليق مباشرة. عندما يُصرِّف (compile) الحاسوب ملفات الشيفرة المصدرية، فإنه يتجاهل تعليقات Javadoc مثلما يتجاهل أي تعليق عادي آخر. تَتوفَّر الأداة javadoc، والتي تَقْرأ ملفات الشيفرة المصدرية، لتَستخرِج منها تعليقات Javadoc، ثم تُنشِئ مجموعة من صفحات الانترنت التي تَتضمَّن تلك التعليقات بصياغة متناسقة ومترابطة. لاحظ أن تلك الأداة تَستخرِج افتراضيًا المعلومات المُتعلِّقة بكُلًا من الأصناف العامة والبرامج الفرعية العامة والمُتَغيِّرات الأعضاء العامة فقط، أي تلك المُصرَّح عنها باِستخدَام المُبدِّل public، ومع ذلك فهي تَسمَح بإنشاء تَوْثيق للعناصر غير العامة (non-public) من خلال خيار خاص. إذا لم تَعثُر الأداة javadoc على أية تعليقات Javadoc لعنصر معين، فإنها تُنشِئ واحدًا افتراضيًا يَتكوَّن من مُجرد معلومات بسيطة عن ذلك العنصر، مثل اسم المُتَغيِّر العضو ونوعه في حالة كان العنصر مُتَغيِّر عضو، أو كُلًا من اسم البرنامج الفرعي، ونوع القيمة المُعادة منه وقائمة مُعامِلاته في حالة كان العنصر برنامجًا فرعيًا. تعدّ تلك المعلومات مُجرد معلومات صياغية (syntactic)، أما لإضافة معلومات دلالية (semantics) واقعية، فلابُدّ من كتابة تعليق Javadoc. كمثال، يُمكِنك فَحْص "توثيق Javadoc للصنف TextIO". اُنشأت صفحة التَوْثيق تلك باِستخدَام أداة javadoc مع ملف الشيفرة المصدري TextIO.java. إذا حَمَّلت نسخة الكتاب المتاحة عبر الإنترنت، ستَجِدْ ذلك التوثيق بالمجلد TextIO_Javadoc. يُعدّ استخدام الترميز * ببداية كل سطر ضِمْن تعليقات Javadoc أمرًا اختياريًا؛ حيث تَحذفه أداة javadoc على أية حال. بالإضافة إلى النص العادي، قد تَحتوِي تعليقات Javadoc على ترميزات (codes) خاصة، مثل أوامر HTML الترميزية (HTML mark-up commands). تُستخدَم لغة HTML عمومًا لإنشاء صفحات الانترنت، ولمّا كانت تعليقات Javadoc مَعنيَّة بالظهور على تلك الصفحات، فإن أداة javadoc تَنسَخ أي أوامر HTML بالتعليقات إلى صفحات الانترنت التي تُنشئها. لن يتناول الكتاب أيّ شرح تفصيلي عن HTML، لكن، كمُجرَّد مثال، تستطيع مثلًا إضافة <p> للإشارة إلى بداية فقرة جديدة. في حالة غياب أوامر HTML، تتجاهل الأداة الأسطر الفارغة والفراغات (spaces) الإضافية الموجودة بالتعليق. انتبه أيضًا للمحرفين & و <؛ لأن لهما معنًى خاصًا بلغة HTML، ولذا لا ينبغي اِستخدَامهما بتعليقات Javadoc لمعنى غير تلك المَعانِي، ولكن يُمكِن كتابتهما باِستخدَام & و < على التوالي. يُوفِّر نظام التوثيق Javadoc ما يُعرَف باسم الوُسوم التوثيقية (doc tags). تُكتَب تلك الوسوم ضِمْن تعليقات Javadoc، بحيث تُعالجها أداة javadoc بعدّها نوعًا من الأوامر الخاصة. يبدأ أيّ وَسم تَوْثيقي (doc tag) بالمحرف @ مَتبوعًا باسمه. سنتناول ٤ وسوم (tags) فقط، هي كالتالي: الوُسوم @author و @param و @return و @throws. يُستخدَم الوسم @author فقط مع الأصناف، وينبغي أن يُتبَع باسم المؤلف. تُستخدَم الوسوم الثلاثة الاخرى بتعليقات Javadoc التي تستهدف البرامج الفرعية بهدف تَوْفِير معلومات عن مُعاملاتها، وقيمها المُعادة، والاعتراضات (exceptions) التي قد تبلِّغ عنها. لابُدّ أن تُوْضَع تلك الأوسمة بنهاية التعليق، أي بَعْد وصف البرنامج الفرعي نفسه. تُكتَب بالصيغ (syntax) التالية: @param <parameter-name> <description-of-parameter> @return <description-of-return-value> @throws <exception-class-name> <description-of-exception> يُمكِن أن تمتد الأوصاف و و إلى عدة أسطر؛ حيث ينتهي أي وصف إما ببداية الوَسْم التوثيقي (doc tag) التالي أو بنهاية تعليق Javadoc بالكامل. يُمكِنك اِستخدَام الوَسْم @param لوصف كل مُعامِل (parameter) يَستقبِله البرنامج الفرعي، وكذلك الوَسْم @throws بقدر ما تريد تَوْثيقه من أنواع الاعتراضات (exception) المختلفة. وأخيرًا الوَسْم @return والذي يُفْترَض كتابته للبرامج الفرعية التي تُعيد قيمة فعلية لا void. لا يُشترَط عمومًا كتابة تلك الوسوم (tags) بترتيب معين. اُنظر المثال التالي والذي يَستخدِم وُسوم التَوْثيق (doc tag) الثلاثة: /** * يحسب هذا البرنامج الفرعي مساحة المستطيل بفرض استقباله * لكلا من طول وعرض المستطيل. * ينبغي أن يكون كلا من طول وعرض المستطيل الممررين قيمة موجبة * @param width طول أحد جوانب المستطيل * @param height طول الجانب الآخر من المستطيل * @return مساحة المستطيل * @throws IllegalArgumentException إذا كان عرض أو طول المستطيل قيمة سالبة */ public static double areaOfRectangle( double height, double width ) { if ( width < 0 || height < 0 ) throw new IllegalArgumentException("Sides must have positive length."); double area; area = width * height; return area; } حَاوِل أن تَستخدِم تعليقات Javadoc بالشيفرة الخاصة بك، حتى لو لَمْ تَكُن تَنوِي إنشاء صفحة انترنت للتوثيق (documentation)؛ لأنها تُعدّ الصيغة القياسية لكتابة التوثيقات، لذا ستَكُون مألوفة لغالبية مُبرمجي الجافا. أما إذا أردت إنشاء صفحة انترنت للتَوْثيق، فستحتاج إلى اِستخدَام أداة javadoc، وهي متاحة كأمر بعُدة تطوير الجافا (JDK) التي ناقشناها بالقسم ٢.٦. تستطيع عمومًا اِستخدَام أداة javadoc بواجهة سطر الأوامر (command line interface) بنفس الطريقة التي تَستخدِم بها أوامر مثل javac و java. لاحِظ أنه يُمكِن تطبيق توثيق Javadoc أيضًا داخل بيئات التطوير المتكاملة (integrated development environments)، تُعرَف اختصارًا باسم IDE، التي تحدثنا عنها بالقسم ٢.٦. لن نَتَعرَّض لأيّ من تلك التفاصيل هنا، حيث يُمكِنك ببساطة مراجعة توثيق بيئة البرمجة (programming environment) الخاصة بك. الاستيراد الساكن (static import) كنقطة أخيرة بهذا القسم، سنتناول امتدادًا للمُوجِّه (directive) import. رأيت بالفعل كيف مَكَّنَك المُوجِّه import من الإشارة إلى صَنْف مثل java.util.Scanner باِستخدَام الاسم البسيط للصَنْف Scanner. لكنك ما زلت مُضطرًا لاِستخدَام الأسماء المُركَّبة للإشارة إلى المُتَغيِّرات الأعضاء الساكنة (static member variables) مثل System.out، والتوابع الساكنة (static methods) مثل Math.sqrt. في الواقع، تَتَوفَّر صياغة آخرى من المُوجِّه import، تَستورِد الأعضاء الساكنة (static members) الموجودة بصَنْف معين بنفس الطريقة التي يَستورَد بها المُوجِّه import الأصناف (classes) من حزمة (package). تُسمَى تلك الصياغة من المُوجِّه باسم الاستيراد الساكن (static import). فمثلًا، لاستيراد اسم عضو ساكن (static member) واحد من صَنْف، نَكتُب الصيغة (syntax) التالية : import static <package-name>.<class-name>.<static-member-name>; بالمثل، قد تُكتَب الصيغة التالية لاستيراد جميع الأعضاء الساكنة العامة (public static members) ضِمْن صَنْف معين: import static <package-name>.<class-name>.*; على سبيل المثال، إذا كَتَبَت المُوجِّه import التالي قَبْل تعريف صَنْف (class definition) معين: import static java.lang.System.out; فإنك تستطيع بَعْدها -ضِمْن ذلك الصَنْف- اِستخدَام الاسم البسيط out بدلًا من الاسم المُركَّب System.out، وكذلك كتابة out.println بدلًا من System.out.println. إذا كنت ستَستخدِم الصَنْف Math بكثرة ضِمْن صَنْف معين، فلربما قد تُفضِّل اِستِباق تعريف ذلك الصنف بالمُوجِّه import التالي: import static java.lang.Math.*; وهو ما سيَسمَح لك بكتابة sqrt بدلًا من Math.sqrt، وكتابة log بدلًا من Math.log، وأيضًا PI بدلًا من Math.PI، وغيره. يُمكِنك أيضًا استيراد الدالة getlnInt من الصنف TextIO باِستخدَام: import static textio.TextIO.getlnInt; تنبيه: يتَطلَّب مُوجِّه الاستيراد الساكن (static import directive) تَخْصيص اسم الحزمة حتى مع الأصناف المُعرَّفة بالحزمة القياسية java.lang، مما يَعنِي أنك لن تَكُون قادرًا على تَّنْفيذ أيّ استيراد ساكن (static import) من صَنْف مُعرَّف بالحزمة الافتراضية (default package)؛ لأن ليس لها اسم. ترجمة -بتصرّف- للقسم Section 6: APIs, Packages, Modules, and Javadoc من فصل Chapter 4: Programming in the Large I: Subroutines من كتاب Introduction to Programming Using Java.1 نقطة
-
قُدرتك على اِستيعاب طريقة عمل البرامج، وفهمها هو أمر جيد بلا شك، ولكنه يختلف تمامًا عن تَصميمها بحيث تُنفِّذ مُهِمّة معينة. لقد ناقشنا بالقسم ٣.٢ كيفية تَطْوير الخوارزميات بطريقة مَنهجية باستخدام أسلوب التصميم المُتدرج (stepwise refinement) والشيفرة الوهمية (pseudocode). التصميم المُتدرج هو عملية تَقَع ضِمْن استراتيجيات التصميم من أعلى لأسفل (top-down)، أيّ لابُدّ عمومًا من وجود نقطة معينة تَتَوقَّف عندها عن عملية التحسِّين التدريجي لخوارزمية الشيفرة الوهمية (pseudocode algorithm)، وتُحوِّل عندها تلك الخوارزمية مباشرة إلى شيفرة برنامج فعليّة. بدون البرامج الفرعية (subroutines)، ستُمثِل تلك النقطة مستوًى بدائيًا جدًا من العمليات، كتَعْليمَات الإِسْناد (assignment statements)، وعمليات الدَخْل والخَرْج، أما في حالة تَوفُّر البرامج الفرعية المسئولة عن إنجاز بعض المَهَامّ (tasks) المُحدَّدة، فسيَكُون بإمكانك التَوقُّف عن التحسِّين بمجرد تعبيرك عن الخوارزمية بنفس المصطلحات التي تَستخدِمها البرامج الفرعية. يَعنِي ذلك أننا قد أضفنا عنصر، هو بالأساس يَتَّبِع أسلوب التصميم من أسفل لأعلى (bottom-up)، إلى استراتيجيات التصميم من أعلى لأسفل (top-down approach) الخاصة بالتصميم المُتدرج. بفَرْض وجود مشكلة معينة، تستطيع الآن البدء بكتابة بعض البرامج الفرعية (subroutines) المسئولة عن إنجاز بعض المَهَامّ المُتَعلِّقة بموضوع البرنامج، بحيث تُصبِح تلك البرامج بمثابة صندوق من الأدوات الجاهزة (toolbox) يُمكِنك دَمْجها إلى الخوارزمية أثناء تَطْويرها، أو حتى قد تستطيع شراء صندوق أدوات برمجي جاهز (software toolbox)، كُتب بواسطة شخص آخر، بحيث يَتَضمَّن ذلك الصندوق مجموعة من البرامج الفرعية، التي ستَكُون بمثابة صندوقًا أسودًا (black boxes)، يُمكِنك تَوظِّيفه ضِمْن المشروع الخاص بك. بإمكانك حتى استخدام البرامج الفرعية مباشرة ضِمْن أساليب التصميم من أعلى لأسفل (top-down approach) الأكثر صرامة، بمعنى أنه وبينما تُحسِّن الخوارزمية بالطريقة المُعتادة، يُمكِنك أن تُعبر عن مُهِمّة فرعية (sub-task) معينة ضِمْن الخوارزمية بصورة استدعاء لبرنامج فرعي. يُصبِح تَطْوير ذلك البرنامج الفرعي عندها مشكلة مُستقلة تستطيع العمل عليها بشكل منفصل. لا يَتَعدَى اتباع تلك الطريقة أكثر من مُجرَّد تَقسِّيم للمشكلة الرئيسية إلى مشكلات صغيرة مُنفصلة، أيّ أنها ما تزال تَقَع ضِمْن استراتيجيات التصميم من أعلى لأسفل؛ لأن تحليلك للمشكلة هو الذي وَجَّهك لكتابة البرامج الفرعية. في المقابل، تَبدأ استراتيجيات التصميم من أسفل لأعلى (bottom-up approach) بكتابة البرامج الفرعية المُتَعلِّقة بموضوع البرنامج، أو الحصول عليها بطريقة ما، بحيث تَعَمَل تلك البرامج الفرعية بمثابة أساس أو قاعدة تُستخدَم لبناء حل للمشكلة الأساسية. الشروط المسبقة (precondition) والشروط اللاحقة (postcondition) تَعََمَل البرامج الفرعية (subroutines) عمومًا كلَبِنات أساسية (building blocks) ضِمْن البرنامج الأساسي، لذا لابُدّ أن تَكُون طريقة تَفْاعُلها مع ذلك البرنامج واضحة. تُحدِّد المواصفة الاصطلاحية (contract) لأيّ برنامج فرعي عمومًا طريقة التَفْاعُل تلك، وهو ما ناقشناه بالقسم ٤.١، ويُمكِن كتابتها باِستخدَام ما يُعرَف باسم الشروط المُسَبَّقة (precondition) واللاحقة (postcondition). لابُدّ أن تَكُون الشروط المُسَبَّقة (precondition) لأي برنامج فرعي (subroutine) مُتحقِّقة عند استدعائه. على سبيل المثال، إحدى الشروط المُسَبَّقة (precondition) للدالة Math.sqrt(x) المبنية مُسْبَّقًا (built-in function) يَتمثَل في ضرورة أن تَكُون القيمة المُمرَّرة للمُعامِل (parameter) x أكبر من أو تُساوِي الصفر؛ لأنه لا يُمكِن بطبيعة الحال حِسَاب الجذر التربيعي لعدد سالب. عمومًا، يُمثِل الشَّرْط المُسَبَّق (precondition) بمواصفة اصطلاحية معينة إلزامًا على مُستدعِي (caller) البرنامج الفرعي، أيّ أنه في حالة استدعائك لبرنامج فرعي معين دون تَوْفِية شَّرْطه المُسَبَّق، فما من سبب يَدْفَعك لتَوقع إِنجازه للمُهِمّة بشكل ملائم، فلرُبما يَنهار البرنامج أو يَكتفِي بإعادة نتائج غير صحيحة؛ لأنك ببساطة لم تَلتزِم بجانبك من الاتفاق، ولهذا لا تَلوّمن إلا نفسك. في المقابل، الشروط اللاحقة (postcondition) هي بمثابة الجانب الآخر من المواصفة الاصطلاحية (contract)، حيث تُمثِل إلزامًا على البرنامج الفرعي ذاته، أي بفَرْض اِستيفاء الشروط المُسَبَّقة (preconditions) لبرنامج فرعي معين، وعدم احتوائه على أية أخطاء برمجية (bugs)، فلابُدّ من تَحقُّق شروطه اللاحقة بعد الاستدعاء. على سبيل المثال، الشَّرْط اللاحق للدالة Math.sqrt(x) يَتمثَل في ضرورة تَساوِي كُلًا من مربع القيمة المُعادة من تلك الدالة (function) وقيمة المُعامِل (parameter) المُمرَّرة عند استدعاء البرنامج الفرعي، وهو ما سيَكُون صحيحًا فقط في حالة استيفاء شَّرْطها المُسَبَّق بخصوص كَوْن قيمة المُعامِل أكبر من أو تُساوِي الصفر. مثال آخر هو البرنامج الفرعي System.out.print(x) المَبنِي مُسْبَّقًا، والذي يَتَمثَل شَّرْطه اللاحق (postcondition) بطباعة قيمة مُعامِله (parameter) المُمرَّرة على الشاشة. عادة ما تَضَع الشروط المُسَبَّقة (preconditions) لأي برنامج فرعي قيودًا على قيم مُعاملاته، مثل البرنامج الفرعي Math.sqrt(x)، لكن لا يَقْتصِر دورها في الواقع على ذلك. قد تُشيِر تلك الشروط أيضًا إلى المُتَغيِّرات العامة (global variables) المُستخدَمة بالبرنامج الفرعي، أو قد تُحدِّد الحالة (state) التي ينبغي أن يَكُون عليها البرنامج عند استدعاء البرنامج الفرعي، وهو ما يَكُون مفيدًا إذا كانت عملية استدعاء ذلك البرنامج صالحة فقط بأوقات معينة. من الناحية الآخرى، يُحدِّد الشَّرْط اللاحق (postcondition) لأي برنامج فرعي مُهِمّته المُوكَلة إليه. فمثلًا، ينبغي أن يَتضمَّن الشَّرْط اللاحق لأي دالة (function) القيمة المُعادة منها. تُوصَف البرامج الفرعية أحيانًا باِستخدَام تعليقات (comments)، والتي ينبغي أن تُحدِّد شروط ذلك البرنامج المُسَبَّقة (preconditions) واللاحقة (postconditions). عندما تَستخدِم برنامجًا فرعيًا (subroutine) مكتوب مُسْبَّقًا، ستُخبرك تلك الشروط بكيفية اِستخدَام البرنامج بالإضافة إلى تِبيان الغرض منه. في المقابل، عندما تَكْتُب برنامجًا فرعيًا، ستَمنَحك تلك الشروط توصيفًا دقيقًا عما هو مُتوقَّع من ذلك البرنامج. سنَتَعرَّض خلال القسمين الفرعيين التاليين لمثال بسيط، سنَستخدِم فيه التعليقات لهذا الغرض، وستَكُون مَكْتوبة بصياغة تعليقات Javadoc مع عَنونة كُلًا من الشروط المُسَبَّقة واللاحقة. يرى كثير من علماء الحاسوب ضرورة إضافة وُسوم تَوْثيق جديدة @precondition و @postcondition إلى نظام Javadoc لعَنونة الشروط المُسَبَّقة واللاحقة بشكل صريح، ولكن لَمْ يَحدُث ذلك حتى الآن. مثال عن عملية التصميم سنُصمِّم الآن برنامجًا بالاعتماد على البرامج الفرعية بصورة أساسية، بمعنى أننا سنَستخدِم بعض البرامج الفرعية (subroutines) التي سُبق كتابتها كلَبِنة أساسية (building block)، كما أننا سنُصمِّم بعض البرامج الفرعية الجديدة التي قد نحتاجها لإكمال المشروع. فيما يَتعلَّق بالبرامج المَكْتوبة مُسْبَّقًا، سنَستخدِم واجهة برمجة تطبيقات (API) تَحتوِي على صنفين (classes) -كان الكاتب قد كَتَبَهما-، الصَنْف الأول هو Mosaic.java والذي بدوره يَعتمِد على الصنف الثاني MosaicCanvas.java. لاحظ ضرورة تَوفِّير كُلًا من الصنفين Mosaic و MosaicCanvas أثناء تَصْرِيف (compile) البرنامج وتَشْغِيله، مما يَعنِي وجود الملفين Mosaic.java و MosaicCanvas.java -أو ملفات الصَنْفين بَعْد التَصْرِيف- بنفس مجلد الصَنْف المُعرِّف للبرنامج. يَسمَح لك الصَنْف Mosaic بالتَعْامُل مع نافذة (window) مُكوَّنة من مجموعة من المستطيلات الصغيرة المُلوَّنة، والمُرَتَّبة بصورة صفوف وأعمدة، حيث يَتضمَّن أكثر من عضو برنامج فرعي ساكن (static member subroutines) يُمكِن اِستخدَامها لأغراض فَتْح النافذة، وغَلْقها، بالإضافة إلى التَلاعب بمُحتوياتها. يُوفِّر الصَنْف بطبيعة الحال صندوق أدوات (toolbox) أو واجهة برمجة تطبيقات (API) تَتضمَّن تلك المجموعة من البرامج (routines)، والتي نسْتَعْرِض بالأسفل بعضًا منها، مُوثَّقة باِستخدَام تعليقات Javadoc. تَذَكَّر أن تعليق Javadoc يَسبِق العنصر المَعنِي بذلك التعليق. /** * افتح نافذة mosaic على الشاشة * ينبغي أن يُستدعى هذا التابع قبل أي تابع آخر ضمن الصنف Mosaic * سينتهي البرنامج عندما يغلق المستخدم النافذة * * الشرط المسبق: المعاملات rows و cols و h و w هي أعداد صحيحة موجبة * * الشرط اللاحق: تفتح نافذة على الشاشة والتي يمكنها عرض صفوف وأعمدة * من المستطيلات الملونة بحيث يكون عرض المستطيل يساوي w وطوله يساوي h * كما أن عدد الصفوف هو قيمة المعامل الأول الممررة بينما عدد الأعمدة * هو قيمة المعامل الثاني. مبدئيًا، تكون جميع المستطيلات سوداء * * ملحوظة: الصفوف مرقمة من 0 وحتى rows - 1 بينما الأعمدة مرقمة * من 0 وحتى cols - 1 */ public static void open(int rows, int cols, int h, int w) /** * ضبط لون أحد مربعات النافذة * * الشرط المسبق: لابد أن كون المعاملين row و col ضمن النطاق المسموح * به لرقمي الصف والعمود، كما لابد أن تقع كلا من المعاملات r و g و b بين العددين 0 و 255 * الشرط اللاحق: سيضبط لون المربع المخصص عن طريق رقمي الصف والعمود * الممررين إلى اللون المحدد عبر المعاملات الثلاثة r و g و b * بحيث تعطي هذه المعاملات قيم اللون الأحمر والأخضر والأخضر * بنظام RGB للمربع، قمثلًا يعطي المعامل r قيمة اللون الأحمر بحيث * تشير القيمة 0 إلى انعدام اللون الأحمر بينما تشير القيمة 255 إلى أكبر قدر ممكن من اللون */ public static void setColor(int row, int col, int r, int g, int b) /** * جلب قيمة اللون الأحمر بنظام RGB لأحد المربعات * * الشرط المسبق: لابد أن يقع كُلا من المعاملين row و col ضمن النطاق المسموح به * الشرط اللاحق: إعادة قيمة اللون الأحمر بنظام RGB للمربع المخصص * كعدد صحيح يقع بين العددين 0 و 255 */ public static int getRed(int row, int col) /** * تعمل بنفس طريقة الدالة getRed */ public static int getGreen(int row, int col) /** * تعمل بنفس طريقة الدالة getRed */ public static int getBlue(int row, int col) /** * لإبطاء تنفيذ البرنامج عبر الانتظار قليلًا * * الشرط المسبق: لابد أن يكون المعامل milliseconds عددا موجبا * الشرط اللاحق: سيتوقف البرنامج مؤقتًا لمدة تساوي الزمن الممرر * بوحدة المللي ثانية */ public static void delay(int milliseconds) تَذَكَّر أن البرامج الفرعية -بالأعلى- هي أعضاء (members) ضِمْن الصَنْف Mosaic، ولهذا ينبغي أن تَتضمَّن أسماء تلك البرامج (routine) اسم الصَنْف ذاته عند اِستدعائها بمكان يَقَع خارج الصنف Mosaic. على سبيل المثال، اِستخدِم الاسم Mosaic.isOpen() بدلًا من الاسم isOpen. لا تُحدِّد تعليقات البرامج الفرعية -بالأعلى- ما سيَحدُث في حالة عدم استيفاء شروطها المُسَبَّقة (preconditions). على الرغم من أن البرامج الفرعية (subroutine)، في العموم، غير مُلزَمة فعليًا بما هو مَكْتوب ضِمْن مواصفاتها الاصطلاحية (contract)، فسيَكُون من الجيد مَعرِفة ما سيَحدُث في مثل تلك الحالات. على سبيل المثال، يُبلِّغ البرنامجين الفرعيين setColor() أو getRed() عن حُدوث اعتراض من النوع IllegalArgumentException في حالة عدم استيفاء شَّرْطهما المُسَبَّق: "ضرورة وقوع كلًا من row و col ضِمْن النِطاق المَسموح به لرَقمي الصف والعمود على الترتيب." تَسمَح مَعرِفتك لمثل تلك الحقيقة بكتابة برامج يُمكِنها التقاط (catch) ذلك الاعتراض (exception)، ومُعالجته، ولهذا سيَكُون من المفيد تَوْثيق (document) تلك المعلومة من خلال إضافة الوَسْم التوثيقي (doc tag) @throws إلى تعليق Javadoc. تَتَبقَّى أسئلة آخرى عديدة تَتَعلَّق بكيفية تَصرُّف البرنامج الفرعي ضِمْن حالات معينة. على سبيل المثال، ماذا سيَحدُث إذا استدعينا البرنامج الفرعي Mosaic.open() بينما هنالك نافذة مفتوحة بالفعل على الشاشة؟ في تلك الحالة، سيتجاهل البرنامج الفرعي عملية الاستدعاء الثانية. في الواقع، يَصعُب عادة إعداد تَوْثيق كامل على تلك الشاكلة، وأحيانًا ستحتاج إلى مُجرَّد تجربة حالة معينة لترى بنفسك كيفية تَصرُّفها، أو قد تَضطرّ أحيانًا للإطلاع على كامل الشيفرة المصدرية (source code) في حالة تَوفُّرها. تَتلخَّص فكرة البرنامج الذي سنقوم بتَطويره بكتابة تحريكة (animation) تَعتمِد على الصنف Mosaic. ببساطة، سنَملْئ نافذة بمربعات مُلوَّنة عشوائيًا، وبعدها، سنُغيِّر تلك الألوان عشوائيًا ضِمْن حَلْقة تَكْرار (loop) تستمر طالما كانت النافذة مفتوحة. قد تَعنِي عبارة "تَغْيِير الألوان عشوائيًا" الكثير من الأشياء المختلفة. المقصود هنا هو إِحداث ما يُشبِه "تشويش (disturbance)" يتحرك عبر النافذة، بحيث يُغيِّر ذلك التشويش من لون أيّ مربع يُواجهه. تُوضِح الصورة التالية ما قد تبدو عليه النافذة بلحظة معينة: مع اعتمادنا على البرامج الفرعية (routines) ضِمْن الصنف Mosaic، نستطيع كتابة التَصوُّر المَبدئي للبرنامج كالتالي: // افتح نافذة Mosaic Open a Mosaic window // اِملئ النافذة بألوان عشوائية Fill window with random colors // تجول وغير لون المربعات عشوائيًا Move around, changing squares at random تبدو الخطوة "اِملئ النافذة بألوان عشوائية" كمُهِمّة (task) مُترابطة يُمكِن فصلها بصورة مستقلة، ولهذا سنَكتُب برنامجًا فرعيًا (subroutine) يَتولَى مسئولية تَّنْفيذ تلك المُهِمّة. في المقابل، يُمكِن إضافة مزيد من التفاصيل للخطوة الثالثة بحيث تُكتَب على عدة خطوات: اِبدأ من منتصف النافذة، ثُمَّ اِستمر بالتحرك إلى مربعات جديدة، وغَيّر لون تلك المربعات. ينبغي الاستمرار بتَّنْفيذ تلك الخطوات طالما كانت النافذة مفتوحة. يُمكِننا إعادة كتابة الخوارزمية (algorithm) لتُصبِح كالتالي: // افتح نافذة Mosaic Open a Mosaic window // اِملئ النافذة بألوان عشوائية Fill window with random colors // اضبط المَوضع الحالي إلى المربع بمنتصف النافذة Set the current position to the middle square in the window // طالما كانت النافذة مفتوحة As long as the mosaic window is open: // غير لون المربع بالمَوضع الحالي عشوائيًا Randomly change color of the square at the current position // حرك الموضع الحالي لأعلى أو لأسفل أو لليسار أو لليمين عشوائيًا Move current position up, down, left, or right, at random سنَستخدِم مُتَغيِّرين من النوع الصحيح int هما currentRow و currentColumn؛ لتَمثيِل "المَوْضِع الحالي (current position)"، سيَحمِلان رقمي الصف (row number) والعمود (column number) للمربع الحالي الذي يُطبَق عليه التشويش (disturbance). لما كانت نافذة mosaic مُكوَّنة من ١٦ صف و ٢٠ عمود من المربعات، يُمكِننا تهيئة قيمة "المَوضِع الحالي" المَبدئية إلى منتصف النافذة عن طريق ضَبْط المُتَغيِّرين currentRow و currentColumn إلى القيمتين ٨ و ١٠ على الترتيب. سنَلجأ لاستخدام البرنامج الفرعي Mosaic.open() لفَتْح النافذة، كما سنَكتُب برنامجين فرعيين (subroutines) إضافيين لإِنجاز مُهِمّتين ضِمْن حَلْقة التَكْرار while بحيث يَظلّ البرنامج main() بسيطًا. نستطيع الآن تَحْوِيل الخوارزمية (algorithm) إلى الشيفرة التالية بلغة الجافا: Mosaic.open(16,20,25,25) fillWithRandomColors(); currentRow = 8; // الصف الواقع بمنتصف النافذة currentColumn = 10; // العمود الواقع بمنتصف النافذة while ( true ) { // ينتهي البرنامج عند غلق النافذة changeToRandomColor(currentRow, currentColumn); randomMove(); } سنُجرِي أيضًا تَعْديلًا إضافيًا، وهو إِبطاء التحريكة (animation) قليلًا، ولذلك سنُضِيف السطر Mosaic.delay(10); إلى حَلْقة التَكْرار while. انتهينا الآن من إِعداد البرنامج main()، لكننا نَحتاج لكتابة البرامج الفرعية fillWithRandomColors() و changeToRandomColor(int,int) و randomMove() لنُكمِل البرنامج. تُعدّ عملية كتابة كل برنامج منها بمَثابة مُهِمّة صغيرة مُنفصلة. سنبدأ بالبرنامج الفرعي fillWithRandomColors() والمَسئول عن ضمان تَحقُّق الشَّرْط اللاحق (postcondition) التالي: "سيَتغيَّر لون كل مربع بالنافذة عشوائيًا." يُمكِننا كتابة خوارزمية (algorithm) لإِنجاز تلك المُهِمّة بأسلوب الشيفرة الوهمية (pseudocode) كالتالي: // لكل صف For each row: // لكل عمود For each column: // غير لون المربع بذلك الصف والعمود إلى لون عشوائي set the square in that row and column to a random color يُمكِن تَّنْفيذ (implement) السَطْرين "لكل صف"، و"لكل عمود" -بالأعلى- باِستخدَام حَلْقة التَكْرار for. أما بخصُوص السطر الأخير، فلقد قَررنا بالفعل كتابة البرنامج الفرعي changeToRandomColor(int,int) المَسئول عن ضَبْط اللون. لاحِظ أن إِمكانية إعادة اِستخدَام برنامج فرعي معين بعدة مَواضِع يُعدّ أحد أهم مَكاسِب اِستخدَام البرامج الفرعية. يُمكِننا الآن كتابة البرنامج الفرعي fillWithRandomColors() بلغة الجافا كالتالي: static void fillWithRandomColors() { int row, column; for ( row = 0; row < 16; row++ ) for ( column = 0; column < 20; column++ ) changeToRandomColor(row,column); } سننتقل الآن إلى البرنامج الفرعي التالي changeToRandomColor(int,int). يُوفِّر الصَنْف Mosaic بالفعل التابع (method) Mosaic.setColor()، والمُستخدَم لتَغْيِير لون المربع.لمّا كنا نُريد لونًا عشوائيًا، فسنحتاج إلى اختيار قيم عشوائية لكل من r و g و b، والتي ينبغي أن تَكُون أعدادًا صحيحة (integers) مُتراوحة بين العددين ٠ و ٢٥٥، وفقًا للشَّرْط المُسَبَّق (precondition) للبرنامج الفرعي Mosaic.setColor()، ولهذا سنَستخدِم المُعادلة (int)(256*Math.random()) لاختيار مثل تلك الأعداد. بالتالي، يُمكِننا كتابة البرنامج الفرعي المُستخدَم لتَغْيِير اللون عشوائيًا كالتالي: static void changeToRandomColor(int rowNum, int colNum) { int red = (int)(256*Math.random()); int green = (int)(256*Math.random()); int blue = (int)(256*Math.random()); Mosaic.setColor(rowNum,colNum,red,green,blue); } وأخيرًا، يُمكِننا الانتقال إلى البرنامج الفرعي randomMove()، والمَسئول عن تحريك التشويش (disturbance) عشوائيًا، لأعلى، أو لأسفل، أو يسارًا، أو يمينًا. سنَستخدِم عددًا عشوائيًا يَتراوح بين القيمتين ٠ و ٣؛ لإجراء الاختيار ما بين الاتجاهات الأربعة، فمثلًا، عندما تَكُون قيمة العدد مُساوِية للصفر، سيَتحرَك التشويش باتجاه معين، أما إذا كانت قيمته مُساوِية للواحد، فسيَتحرَك باتجاه آخر، وهكذا. لمّا كنا نَستخدِم المُتَغيِّرين currentRow و currentColumn لتحديد المَوضِع الحالي للتشويش، فإن تحريك ذلك المَوضِع لأعلى يَعنِي بالضرورة إِنقاص قيمة المُتَغيِّر currentRow بمقدار الواحد. يَترك ذلك استفهامًا عما يُفْترَض حُدوثه عندما يَصِل المُتَغيِّر currentRow إلى القيمة -١؛ خُصوصًا وأن ذلك سيَتَسبَّب بإخفاء التشويش خارج النافذة، وهو ما سينتهك الشَّرْط المُسَبَّق (precondition) لكثير من البرامج الفرعية ضِمْن الصنف Mosaic، ولهذا سنُحرِك التشويش إلى الحافة المُضادة من النافذة من خلال ضَبْط قيمة المُتَغيِّر currentRow إلى ١٥ (تَذَكَّر أن النافذة مُكوَّنة من ١٦ صف مُرقَّمَين من ٠ وحتى ١٥). بدلًا من القفز إلى الحافة الآخرى، نستطيع أيضًا تَجاهُل تلك الحالة. يُمكِننا معالجة تحريك التشويش (disturbance) للاتجاهات الثلاثة الأخرى بنفس الطريقة، بحيث نستعين بتَعْليمَة switch لتَحْديد الاتجاه ذاته. اُنظر شيفرة البرنامج الفرعي randomMove(): int directionNum; directionNum = (int)(4*Math.random()); switch (directionNum) { case 0: // تحرك لأعلى currentRow--; if (currentRow < 0) // إذا أصبح الموضع الحالي خارج النافذة currentRow = 15; // قم بتحريكه للحافة المضادة break; case 1: // تحرك لليمين currentColumn++; if (currentColumn >= 20) currentColumn = 0; break; case 2: // تحرك لأسفل currentRow++; if (currentRow >= 16) currentRow = 0; break; case 3: // تحرك لليسار currentColumn--; if (currentColumn < 0) currentColumn = 19; break; } البرنامج انتهينا الآن من كتابة جميع البرامج الفرعية، وتَبقَّى لنا تَجْميعها معًا بحيث نَحصُل على البرنامج كاملًا، بالإضافة إلى كتابة تعليقات Javadoc للصنف ذاته ولبرامجه الفرعية. لاحظ أننا قد عَرَّفنا المُتَغيِّرين currentRow و currentColumn كأعضاء ساكنة (static members) ضِمْن الصنف، وليس كمُتَغيِّرات محليّة (local)؛ وذلك لكَوْنهما مُستخدَمين ضِمْن أكثر من مُجرَّد برنامج فرعي (subroutines) واحد. تَتَوفَّر نسخة من الشيفرة المصدرية للبرنامج بالملف RandomMosaicWalk.java، وانتبه لكَوْنه يَعتمِد على كُلًا من الملفين Mosaic.java و MosaicCanvas.java. /** * يفتح هذا البرنامج نافذة مليئة بالمربعات الملونة عشوائيًا * بحيث يتحرك نوع من "التشويش" عشوائيًا عبر الشاشة ويغير من * لون أي مربع يواجهه بشكل عشوائي. يستمر البرنامج في العمل * طالما كانت النافذة مفتوحة */ public class RandomMosaicWalk { static int currentRow; // رقم الصف المعرض للتشويش static int currentColumn; // رقم العمود المعرض للتشويش /** * يُنشيء برنامج main النافذة ويملؤها بألوان عشوائية * ثم يحرك التشويش بصورة عشوائية عبر النافذة طالما كانت مفتوحة */ public static void main(String[] args) { Mosaic.open(16,20,25,25); fillWithRandomColors(); currentRow = 8; // ابدأ بمنتصف النافذة currentColumn = 10; while (true) { changeToRandomColor(currentRow, currentColumn); randomMove(); Mosaic.delay(10); // احذف هذا السطر لزيادة سرعة التحريكة } } // نهاية main /** * يملأ النافذة بمربعات ملونة عشوائيا * * الشرط المسبق: لابد أن تكون النافذة مفتوحة * الشرط اللاحق: سيصبح كل مربع النافذة ملون بصورة عشوائية */ static void fillWithRandomColors() { int row, column; for ( row=0; row < 16; row++ ) { for ( column=0; column < 20; column++ ) { changeToRandomColor(row, column); } } } // نهاية fillWithRandomColors /** * يغير من لون مربع معين بالنافذة عشوائيًا * * الشرط المسبق: لابد أن يقع رقمي الصف والعمود الممررين ضمن * النطاق المسموح به لأرقام الصف والعمود * الشرط اللاحق: سيتغير لون المربع المخصص بواسطة رقمي الصف والعمود * * @param rowNum رقم الصف للمربع بحيث يبدأ عد الصفوف من الأعلى * @param colNum رقم الصف للمربع بحيث يبدأ عد الأعمدة من اليسار */ static void changeToRandomColor(int rowNum, int colNum) { // اختر قيم عشوائية تتراوح بين 0 و 255 // لقيم الألوان الثلاثة (الأحمر، والأزرق، والأخضر) بنظام الألوان RGB int red = (int)(256*Math.random()); int green = (int)(256*Math.random()); int blue = (int)(256*Math.random()); Mosaic.setColor(rowNum,colNum,red,green,blue); } // نهاية changeToRandomColor /** * يحرك التشويش عبر النافذة * * الشرط المسبق: لابد أن تكون المتغيرات العامة currentRow و * currentColumn ضمن النطاق المسموح به لرقمي الصف والعمود * الشرط اللاحق: يتغير رقمي الصف والعمود إلى أحد المواضع المجاورة * سواء للأعلى أو للأسفل أو لليسار أو لليمين */ static void randomMove() { int directionNum; directionNum = (int)(4*Math.random()); switch (directionNum) { case 0: // move up currentRow--; if (currentRow < 0) currentRow = 15; break; case 1: // move right currentColumn++; if (currentColumn >= 20) currentColumn = 0; break; case 2: // move down currentRow ++; if (currentRow >= 16) currentRow = 0; break; case 3: // move left currentColumn--; if (currentColumn < 0) currentColumn = 19; break; } } // نهاية randomMove } // نهاية الصنف RandomMosaicWalk ترجمة -بتصرّف- للقسم Section 7: More on Program Design من فصل Chapter 4: Programming in the Large I: Subroutines من كتاب Introduction to Programming Using Java.1 نقطة
-
صدقت، جرى التصحيح. كان المثال مكتوبًا بتلك الصورة في المقال الأصلي، ربما لم ينتبه الكاتب لهذا الخطأ وسأحاول تبليغه لتصحيحه في المقال الأصلي أيضًا. شكرًا جزيلًا لك لانتباهك لهذه التفاصيل. @Mhamad1 نقطة
-
يعطيك العافية, مقال مفيد و مبسط. أود الإشارة لخطأ بسيط في فقرة "التوثيق بأداة Javadoc" و بالتحديد في المثال الذي يستخدم الذي وسوم التوثيق (doc tag). باراميتر الدالة يجب أن يكون إسمه height و ليس length ليعمل باقي الكود الموضوع في الدالة.1 نقطة
-
يُخْزَّن البرنامج الفرعي (subroutine) بذاكرة الحاسوب على هيئة سِلسِلة نصية طويلة مُكوَّنة من الرقمين صفر وواحد، ولذلك فإنه لا يَختلف كثيرًا عن البيانات (data)، مثل الأعداد الصحيحة والسَلاسِل النصية والمصفوفات، والتي تُخْزَّن أيضًا بنفس الطريقة. ربما اِعتدت التفكير بكُلًا من البرامج الفرعية والبيانات على أساس أنهما شيئان مختلفان تمامًا، ولكن في الواقع يَتعامَل الحاسوب مع البرنامج الفرعي على أساس أنه مجرد نوع آخر من البيانات. تَسمَح حتى بعض اللغات البرمجية بالتَعامُل مع البرامج الفرعية بنفس الطريقة التي يَتعامَل معها الحاسوب، وهو ما أُضيف إلى الجافا على هيئة تعبيرات لامدا (lambda expressions) منذ الإصدار ٨. أصبحت تعبيرات لامدا أكثر شيوعًا ببرامج الجافا، وهي مفيدة في العموم، ولكن تَبرُز أهميتها بصورة أكبر ببرمجة واجهات المُستخدِم الرُسومية (GUI)، كالتَعامُل مع "أدوات تَحكُّم واجهة المُستخدَم الرسومية JavaFX (JavaFX GUI toolkit)"، وهو ما سنتناوله بالفصل السادس، كما سنَتَعرَّض لها مجددًا بنهاية الفصل الخامس، لذا يُمكِنك تَخطِّي هذا القسم إذا أردت إلى ذلك الحين. الدوال الكيانية (first-class functions) يُمكِننا كتابة دالة لحِسَاب قيمة مربع العدد، بحيث تَستقبِل ذلك العدد كمُعامِل صُّوري أو وهمي (dummy parameter)، كالتالي: static double square( double x ) { return x*x; } كالعادة، لابُدّ من تَخْصيص اسم لتلك الدالة، وفي تلك الحالة كان square، وعليه تُصبِح تلك الدالة جزءًا دائمًا من البرنامج (program)، وهو ما قد لا يَكُون مُلائمًا؛ بالأخص إذا كنت تَنوِي اِستخدَام الدالة مرة واحدة فقط. لامدا (lambda) هو أحد حروف الأبجدية الإغريقية (Greek alphabet)، والذي اِستخدَمه عالم الرياضيات ألونزو تشرتش (Alonzo Church) أثناء دراسته للدوال القابلة للحِسَاب (computable functions). على سبيل المثال، إذا كنا نُريد حِسَاب قيمة مربع عدد، وليكن x، فقد تَظُنّ أن الترميز x2 يُمثِل "دالة" تَحسِب مربع x، لكنه، في الواقع، ليس سوى تعبير (expression) يُمثِل "نتيجة" حِسَاب مربع x. قَدَّمَ ألونزو تشرتش (Alonzo Church) ترميز لامدا (lambda notation) والذي يُمكِن اِستخدَامه لتعريف دالة (function) بدون تَخْصيص اسم لها. بالتحديد اِستخدَم الترميز lambda(x).x2 (في الواقع يُستخدَم الحرف الإغريقي لامدا ذاته وليس الكلمة نفسها) للإشارة إلى "دالة x معطاة بواسطة x2". يُعدّ هذا الترميز دالة مجرَّدة (function literal)، أيّ تُمثِل قيمة من النوع "دالة (function)" بنفس الطريقة التي تُعدّ بها القيمة 42 عددًا صحيحًا مُجرَّدًا (integer literal) يُمثِل قيمة من النوع int. ينبغي أن تَدْفَعك الدوال المُجرَّدة (function literals) إلى التَفكير بالدوال (function) على أنها مُجرَّد نوع آخر من القيم، وعندما تَصِل إلى تلك المرحلة، فإنك ستَكُون قادرًا على تَطْبِيق نفس تلك الأشياء التي اعتدت إجرائها على القيم الآخرى على الدوال أيضًا، كإِسْناد دالة إلى مُتَغيِّر، أو تمرير دالة كمُعامِل (parameter) إلى برنامج فرعي (subroutine)، أو إعادة دالة كقيمة من برنامج فرعي، أو حتى إنشاء مصفوفة دوال (array of functions). تَسمَح بعض لغات البرمجة بالقيام بكل تلك الأشياء، ويُقال عندها أنها تُدعِّم "الدوال الكيانيَّة (first-class functions)" أو أنها تُعامِل الدوال بعدّها كائنات كيانيَّة (first-class objects). تُوفِّر لغة الجافا كل تلك الأشياء من خلال تعبيرات لامدا (lambda expressions)، والتي تَختلِف صيغتها عن الترميز الذي اِستخدَمه ألونزو تشرتش (Alonzo Church)، فهي الواقع، لا تَستخدِم حتى كلمة لامدا (lambda) على الرغم من مُسماها. تُكتَب دالة حِسَاب مربع العدد بتعبير لامدا (lambda expression) بلغة الجافا، كالتالي: x -> x*x يُنشِئ العَامِل -> تعبير لامدا، بحيث تَقَع المُعامِلات الوهمية أو الصُّوريّة (dummy parameter) على يسار العَامِل، بينما يَقَع التعبير (expression) المسئول عن حِسَاب قيمة الدالة على يمينه. قد ترى تعبير لامدا بالأعلى مُمرَّرًا كمُعامِل فعليّ (actual parameter) إلى برنامج فرعي، أو مُسنَد إلى مُتَغيِّر، أو مُعاد من دالة. إذًا، هل الدوال بلغة الجافا كيانيَّة (first-class)؟ لا تَتَوفَّر إجابة واضحة على هذا السؤال، لأن لغة الجافا لا تُدعِّم بعض الأشياء الآخرى المُتوفِّرة ضِمْن لغات برمجية آخرى. فمثلًا، في حين تستطيع إِسْناد تعبير لامدا المُعرَّف بالأعلى إلى مُتَغيِّر اسمه sqr، فإنك لن تستطيع بَعْدها اِستخدَام ذلك المُتَغيِّر كما لو كان دالة فعليّة، أيّ لا تستطيع كتابة sqr(42). لغة الجافا عمومًا هي لغة صارمة في تَحْدِيد النوع (strongly typed)، ولذلك لكي تَتَمكَّن من إِنشاء المُتَغيِّر sqr، فعليك التَّصْريح عنه أولًا وهو ما يَتضمَّن تَخْصيص نوعه، فيا تُرى ما هو ذلك النوع الذي يَتناسب مع قيمة هي عبارة عن دالة؟ تُوفِّر الجافا ما يُعرَف باسم واجهة نوع الدالة (functional interface) لهذا الغرض، وسنناقشها بَعْد قليل. ملاحظة أخيرة: على الرغم من ارتباط مصطلح الدالة (function) بتعبيرات لامدا (lambda expressions) بدلًا من مصطلحات مثل برنامج فرعي (subroutine) أو تابع (method)، فإنها في الواقع لا تَقْتصِر على تمثيل الدوال (functions)، وإنما يُمكِنها تمثيل أي برنامج فرعي (subroutines)، وبصورة عشوائية. واجهات نوع الدالة (functional interfaces) كي تَتَمكَّن من اِستخدَام برنامج فرعي (subroutine) معين، فلابُدّ أن تَعرِف كُلًا من اسمه، وعدد المُعامِلات (parameters) التي يَستقبِلها، وأنواعها، بالإضافة إلى نوع القيمة المُعادة (return type) من ذلك البرنامج الفرعي. تُوفِّر لغة الجافا ما يُعرَف باسم واجهات نوع الدالة (functional interface)، وهي أَشْبَه بالأصناف (class)، حيث تُعرَّف بملف امتداده هو .java كأي صَنْف، وتَتضمَّن تلك المعلومات المذكورة بالأعلى عن برنامج فرعي وحيد. انظر المثال التالي: public interface FunctionR2R { double valueAt( double x ); } تُصرِّح الشيفرة بالأعلى عن واجهة نوع الدالة FunctionR2R، والموجودة بملف يَحمِل اسم FunctionR2R.java. تُصرِّح تلك الواجهة عن الدالة valueAt، والتي تَستقبِل مُعامِل وحيد من النوع double، وتُعيد قيمة من النوع double. تَفرِض قواعد الصيغة تَخْصيص اسم للمُعامِل -x بالأعلى- على الرغم من أنه في الواقع ليس ذا أهمية هنا، وهو ما قد يَكُون مزعجًا نوعًا ما. ها هو مثال آخر: public interface ArrayProcessor { void process( String[] array, int count ); } تَتضمَّن لغة الجافا الكثير من واجهات نوع الدالة القياسية (standard functional interfaces) بصورة افتراضية. تُعدّ واجهة نوع الدالة Runnable واحدة من أهم تلك الواجهات، وأبسطها، والمُعرَّفة كالتالي: public interface Runnable { public void run(); } سنَستخدِم واجهات نوع الدالة (functional interfaces) الثلاثة بالأعلى للأمثلة التالية بهذا القسم. تُوفِّر لغة الجافا أيضًا ما يُعرَف باسم الواجهات (Interfaces)، والتي هي في الواقع أعم وأشمل، وربما أكثر تعقيدًا من واجهات نوع الدالة (functional interfaces). سنتعلَّّم المزيد عنها بالقسم ٥.٧. سنُقصِر حديثنا بهذا القسم على واجهات نوع الدالة (functional interfaces)؛ لارتباطها بموضوعنا الرئيسي: تعبيرات لامدا (lambda expressions). يُعدّ اسم واجهة نوع الدالة نوعًا (type)، تمامًا كالأنواع String و double، وكأيّ نوع، فإنه يُمكِن اِستخدَامه للتَّصْريح عن المُتَغيِّرات، والمُعامِلات، وكذلك لتَخْصيص نوع القيمة المُعادة (return type) من الدوال، ويُمكِن إِسْناد تعبيرات لامدا (lambda expression) كقيم للمُتَغيِّرات من ذلك النوع. في الواقع، تَتضمَّن واجهة نوع الدالة (functional interface) قالبًا (template) لبرنامج فرعي وحيد، وينبغي لتعبير لامدا (lambda expression) المُسنَد أن يَكُون مُتطابِقًا مع ذلك القالب. تعبيرات لامدا (lambda Expressions) يُعدّ أي تعبير لامدا (lambda expression) تمثيلًا لبرنامج فرعي مجهول الاسم (anonymous subroutine)، أيّ لا يَمتلك اسمًا، ولكنه في المقابل، وكأيّ برنامج فرعي، لديه قائمة من المُعامِلات الصُّوريّة (formal parameter list)، بالإضافة إلى التعريف (definition) نفسه، ويُكتَب عمومًا بالصيغة (syntax) التالية: ( <parameter-list> ) -> { <statements> } كما هو الحال مع البرامج الفرعية العادية، يُمكِن لقائمة المُعامِلات -بالأعلى- أن تَكُون فارغة، أو قد تتكوَّن من تَّصْريح عن مُعامِل (parameter declaration) وحيد أو أكثر، بحيث يُفصَل بين كل تَّصْريح والذي يليه بفاصلة (comma)، وبحيث يَتكوَّن كل تَّصْريح من نوع المُعامِل متبوعًا باسمه. في الواقع، تُبسَّط عادة تلك الصيغة وفقًا لمجموعة من القواعد: أولًا: يُمكِن حَذْف أنواع المُعامِلات إذا كان استنباطها من السِّياق مُمكِنًا. على سبيل المثال، إذا كان هناك تعبير لامدا مُعرَّف على أنه من النوع FunctionR2R، فإن مُعامِل التعبير لابُدّ وأن يَكُون من النوع double، ولذلك فإنه، في تلك الحالة، ليس من الضروري تَخْصيص نوع المُعامِل ضِمْن تعبير اللامدا. ثانيًا، يُمكِن حَذْف الأقواس () حول قائمة المُعامِلات (parameter list) إذا كانت مُكوَّنة من مُعامِل وحيد غَيْر مُحدَّد النوع. ثالثًا، يُمكِن حَذْف الأقواس {} الموجودة على الجانب الأيمن من العَامِل -> إذا كانت تَحتوِي على تَعْليمَة استدعاء برنامج فرعي (subroutine call) وحيدة. أخيرًا، إذا كان الجانب الأيمن من العَامِل -> مكتوبًا بالصيغة { return <expression>; }، فيُمكِن تبسيطها إلى التعبير وحَذْف أي شيء آخر. بفَرْض أننا نريد كتابة دالة لحِسَاب مربع القيم من النوع double، فإن نوع تلك الدالة هو من نفس نوع واجهة نوع الدالة FunctionR2R المُعرَّفة بالأعلى. الآن، إذا كان sqr مُتَغيِّرًا من النوع FunctionR2R، فإننا نستطيع أن نُسنِد تعبير لامدا (lambda expression) كقيمة لذلك المُتَغيِّر، ويُعدّ عندها ذلك التعبير تمثيلًا للدالة المطلوبة. يُمكِن القيام بذلك بأي من الصِيَغ التالية: sqr = (double x) -> { return x*x; }; sqr = (x) -> { return x*x; }; sqr = x -> { return x*x; }; sqr = x -> x*x; sqr = (double fred) -> fred*fred; sqr = (z) -> z*z; تُعرِّف تعبيرات لامدا (lambda expressions) الستة بالأعلى نفس الدالة بالضبط، وأُضيفت آخر تَعْليمَتين (statements) خصيصًا؛ للتأكيد على أن أسماء المُعامِلات غير مهمة، فهى بالنهاية مجرد مُعامِلات وهمية أو صُّوريّة (dummy parameters). لمّا كان المُصرِّف (compiler) على علم بكون المُتَغيِّر sqr من النوع FunctionR2R، ولأن النوع FunctionR2R يتطلَّب مُعامِلًا من النوع double، فقد تَمَكَّنا من حَذْف نوع المُعامِل double. يُمكِن اِستخدَام تعبيرات لامدا فقط ضِمْن السِّياقات التي يستطيع فيها المُصرِّف (compiler) استنباط نوعها، ولذلك لابُدّ من كتابة نوع المُعامِل إذا كان حَذْفه سيَتسبَّب بغموض نوع التعبير. لا يُعدّ المُتَغيِّر sqr -كما هو مُعرَّف بالأعلى- دالة (function) تمامًا، وإنما هو قيمة من النوع FunctionR2R، وبحسب ما هو مُخصَّص بتعريف الواجهة (interface definition) FunctionR2R، فإن ذلك المُتَغيِّر لابُدّ وأن يَحتوِي على دالة تَحمِل اسم valueAt. يمكن استدعاء تلك الدالة من خلال اسمها الكامل sqr.valueAt، فمثلًا يُمكِن كتابة sqr.valueAt(42) أو sqr.valueAt(x) + sqr.valueAt(y). إذا كانت قائمة مُعامِلات تعبير لامدا مُكوَّنة من أكثر من مُعامِل واحد، فإن الأقواس () المُحيطة بقائمة المُعامِلات (parameters list) لم تَعُدْ اختيارية. اُنظر المثال التالي، والذي يَستخدِم واجهة نوع الدالة ArrayProcessor، كما يُبيِّن طريقة كتابة تعبير لامدا عندما يكون تعريفها مُتعدِّد الأسطر (multiline definition): ArrayProcessor concat; concat = (A,n) -> { // الأقواس هنا مطلوبة String str; str = ""; for (int i = 0; i < n; i++) str += A[i]; System.out.println(str); }; // الفاصلة المنقوطة هنا ليست جزءًا من تعبير لامدا // وإنما تشير إلى انتهاء تعليمة الإسناد String[] nums; nums = new String[4]; nums[0] = "One"; nums[1] = "Two"; nums[2] = "Three"; nums[3] = "Four"; for (int i = 1; i < nums.length; i++) { concat.process( nums, i ); } مما سينتج عنه الخَرْج التالي: One OneTwo OneTwoThree OneTwoThreeFour تُصبِح الأمور أكثر تشويقًا عند تمرير تعبير لامدا كمُعامِل فعليّ (actual parameter)، وهو في الواقع الاِستخدَام الأكثر شيوعًا لتعبيرات لامدا. افترض أن لدينا الدالة التالية مثلًا: /** * احسب قيمة التعبير f(start) + f(start+1) + ... + f(end) * حيث f عبارة عن دالة تُستقبل كمعامل * قيمة المعامل end لابد أن تكون أكبر من أو تساوي start */ static double sum( FunctionR2R f, int start, int end ) { double total = 0; for (int n = start; n <= end; n++) { total = total + f.valueAt( n ); } return total; } لمّا كانت f من النوع FunctionR2R، فإن قيمة f عند n تُكتَب على الصورة f.valueAt(n). يُمكِن تمرير تعبير لامدا (lambda expression) كقيمة للمُعامِل الأول عند استدعاء الدالة sum، كالتالي: System.out.print("The sum of n squared for n from 1 to 100 is "); System.out.println( sum( x -> x*x, 1, 100 ) ); System.out.print("The sum of 2 raised to the power n, for n from 1 to 10 is "); System.out.println( sum( num -> Math.pow(2,num), 1, 10 ) ); كمثال آخر، افترض أن لدينا برنامجًا فرعيًا (subroutine) ينبغي أن يُنفِّذ مُهِمّة (task) مُعطاة عدة مرات، يُمكِننا ببساطة تمرير تلك المُهِمّة كمُعامِل من النوع Runnable، كالتالي: static void doSeveralTimes( Runnable task, int repCount ) { for (int i = 0; i < repCount; i++) { task.run(); // نفذ المهمة } } نستطيع الآن طباعة السِلسِلة النصية "Hello World" عشر مرات باستدعاء التالي: doSeveralTimes( () -> System.out.println("Hello World"), 10 ); تَتَكوَّن قائمة مُعامِلات (parameter list) تعبيرات لامدا من النوع Runnable من زوج فارغ من الأقواس ()؛ وذلك لتتماشى مع البرنامج الفرعي المُصرَّح عنه ضِمْن واجهة نوع الدالة Runnable. اُنظر المثال التالي: doSeveralTimes( () -> { int count = 5 + (int)(21*Math.random()); for (int i = 1; i <= count; i++) { System.out.print(i + " "); } System.out.println(); }, 100); على الرغم من أن الشيفرة بالأعلى قد تبدو معقدة نوعًا ما، إلا أنها، في الواقع، مُكوَّنة من تَعْليمَة وحيدة هي تَعْليمَة استدعاء البرنامج الفرعي doSeveralTimes. يَتَكوَّن مُعامِل البرنامج الفرعي الأول من تعبير لامدا (lambda expression) قد امتد تعريفه إلى عدة أسطر، أما مُعامِله الثاني فهو القيمة ١٠٠، وتُنهِي الفاصلة المنقوطة (semicolon) بنهاية آخر سطر تَعْليمَة استدعاء البرنامج الفرعي (subroutine call). اطلعنا على عدة أمثلة تُسنَد فيها تعبيرات لامدا (lambda expression) إلى مُتَغيِّرات، وآخرى تُمرَّر فيها تعبيرات لامدا كمُعامِلات فعليّة (actual parameter). تَبَقَّى الآن أن نراها مُستخدَمة كقيمة مُعادة (return value) من دالة. اُنظر المثال التالي: static FunctionR2R makePowerFunction( int n ) { return x -> Math.pow(x,n); } وبالتالي، ستُعيد makePowerFunction(2) قيمة من النوع FunctionR2R تَحسِب مربع قيمة المُعامِل المُمرَّر، بينما ستُعيد makePowerFunction(10) قيمة من النوع FunctionR2R تَحسِب قيمة المُعامِل المُمرَّر مرفوعًا للأس ١٠. يُوضِح هذا المثال أيضًا أن تعبيرات لامدا (lambda expression) تستطيع اِستخدَام مُتَغيِّرات آخرى إلى جانب مُعامِلاتها، مثل n في المثال بالأعلى. (في الواقع هناك بعض القيود لتَحْدِيد متى يُمكِن القيام بذلك). مراجع التوابع (Method References) لنفْترِض أننا نريد كتابة تعبير لامدا (lambda expression) من النوع FunctionR2R، بحيث يُمثِل دالة تَحسِب الجذر التربيعي. نستطيع كتابته على الصورة x -> Math.sqrt(x)، ولكننا إذا دققنا النظر، سنَجِدْ أن تعبير لامدا، في تلك الحالة تحديدًا، ليس سوى مجرد مُغلِّف (wrapper) بسيط للدالة Math.sqrt الموجودة بالفعل. ولهذا تُوفِّر الجافا ما يُعرَف باسم مَراجِع التوابع (method reference)؛ لاِستخدَامها في الحالات المشابهة (تَذَكَر أن "التابع [method]" هو مجرد كلمة آخرى للإشارة إلى "البرنامج الفرعي [subroutine]" بلغة الجافا). نستطيع مثلًا اِستخدَام المَرجِع التابعي Math::sqrt، والذي يُعدّ اختصارًا لتعبير اللامدا المذكور سلفًا، أينما أَمْكَن اِستخدَام تعبير اللامدا المناظر، كتمريره مثلًا كمُعامِل للدالة sum المُعرَّفة بالأعلى، كالتالي: System.out.print("The sum of the square root of n for n from 1 to 100 is "); System.out.println( sum( Math::sqrt, 1, 100 ) ); فقط لو أَمْكَننا اِستخدَام الاسم Math.sqrt هنا بدلًا من الاستعانة بترميز (notation) جديد ::! في الواقع، الترميز Math.sqrt مُعرَّف بالفعل ضِمْن الصَنْف Math لكن للإشارة إلى مُتَغيِّر اسمه sqrt. يُمكِن عمومًا اِستخدَام مَراجِع التوابع (method reference) بدلًا من تعبيرات لامدا (lambda expression) التي يَقْتصِر دورها على اِستدعاء أحد التوابع الساكنة (static method) الموجودة مُسْبَّقًا. تُكتَب مَراجِع التوابع على الصورة التالية: <classname>::<method-name> لا يَقْتصِر هذا الترميز (notation) على التوابع الساكنة، أي تلك المُنتمية للأصناف (classes)، وإنما يَمْتَدّ إلى تلكم المُنتمية للكائنات (objects) كذلك. على سبيل المثال، إذا كان str متغير من النوع String، فإن str بطبيعة الحال سيَحتوِي على التابع str.length()، وفي هذه الحالة، يُمكِن اِستخدَام المَرجِع التابعي str::length كتعبير لامدا من واجهة نوع الدالة SupplyInt المُعرَّفة كالتالي: public interface SupplyInt { int get( ); } ترجمة -بتصرّف- للقسم Section 5: Lambda Expressions من فصل Chapter 4: Programming in the Large I: Subroutines من كتاب Introduction to Programming Using Java.1 نقطة
-
يُطلَق على البرامج الفرعية (subroutine) التي تُعيد قيمة اسم "الدوال (function)"، والتي يُمكِنها أن تُعيد قيمة من نوع واحد مُحدَّد ضِمْن التعريف ويُسمى النوع المُعاد (return type) من الدالة. بإِمكَانك استدعاء الدوال (function call) في العموم بأيّ مكان يَتوقَّع فيه الحاسوب قيمة (value)، مثل الجانب الأيمن من تَعْليمَات الإِسْناد (assignment statement)، أو كمُعامِل فعليّ (actual parameter) بتَعْليمَات استدعاء البرامج الفرعية، أو كجُزء من تعبير (expression) أكبر. يُمكِن حتى اِستخدَام الدوال من النوع boolean، أي التي تُعيد قيمة من ذلك النوع، مَحل الشَّرْط (condition) ضِمْن تَعْليمَات مثل if و while و for و do..while. يُمكِن أيضًا اِستخدَام تَعْليمَة استدعاء دالة (function call)، كما لو كانت برنامجًا فرعيًا، أيّ كما لو أنها لا تُعيد قيمة. في تلك الحالة، يَتجاهَل الحاسوب القيمة المُعادة من البرنامج الفرعي، وهو ما يَكُون منطقيًا في بعض الأحيان. على سبيل المثال، تَقرأ الدالة TextIO.getln() سَطْرًا مُدْخَلًا مِنْ قِبَل المُستخدِم وتُعيده كقيمة من النوع String. عادة، يُسنَد السَطْر المُعاد من تلك الدالة إلى مُتَغيِّر؛ لكي يُستخدَم لاحقًا، كما في التَعْليمَة name = TextIO.getln();. لكن، يُمكِن اِستخدَام تلك الدالة ضِمْن تَعْليمَة استدعاء برنامج فرعي على الصورة TextIO.getln();، والتي، كالعادة، ستَستمِر بقراءة مُدْخَلات المُستخدِم إلى أن تَصِل إلى محرف العودة إلى بداية السطر (carriage return). لمّا لم تُسنَد القيمة المُعادة من تلك الدالة إلى مُتَغيِّر، أو تُستخدَم ضِمْن تعبير (expression)، فإنها تُستبعَد ببساطة، لذا يَكُون تأثير استدعاء البرنامج الفرعي في تلك الحالة هو مجرد قراءة بعض المُدْخَلات واِستبعَادها، وهو الشيء الذي قد تَرغَب به في بعض الأحيان. تعليمة return بوصولك إلى هنا، فأنت قد تَعلَّمت طريقة استدعاء الدوال (functions) -التي تُعيد قيم فعليّة- مثل Math.sqrt() و TextIO.getInt()، لكنك لَمْ تَكتُب أيّ دالة خاصة بك بَعْد. في الواقع، ستبدو كتابة الدوال أمرًا مألوفًا بالنسبة لك؛ فهي بالنهاية مُشابهة تمامًا لصيغة كتابة أيّ برنامج فرعي (subroutine) عادي باستثناء ضرورة اِستخدَام التَعْليمَة return؛ لتَخْصِيص القيمة التي سيُعيدها البرنامج الفرعي (return value). تُكتَب تَعْليمَة return بالصيغة (syntax) التالية: return <expression>; يُمكِن اِستخدَام تَعْليمَة return بالأعلى داخل تعريف الدوال فقط (function definition)، ولابُدّ لنوع التعبير المَكْتُوب بَعْد التَعْليمَة أن يَتماشى مع النوع المُعاد (return type) المُخصَّص ضِمْن تعريف الدالة، أيّ لابُدّ أن يؤول ذلك التعبير إلى قيمة هي من نوع يُمكِن إِسْناده -بواسطة تَعْليمَة إِسْناد (assignment)- إلى النوع المُعاد (return type) من الدالة والمُخصَّص بتعريفها (definition). عندما يُنفِّذ الحاسوب تَعْليمَة return، فإنه يَحسِب قيمة التعبير المُخصَّص، ثم يُنهِي تَّنْفيذ الدالة، وأخيرًا، يُعيد قيمة التعبير لتُصبِح القيمة المُعادة (return value) من الدالة. اُنظر تعريف الدالة التالي على سبيل المثال: static double pythagoras(double x, double y) { return Math.sqrt( x*x + y*y ); } بفَرْض تَّنْفيذ الحاسوب لتَعْليمَة إِسْناد تَتضمَّن تَعْليمَة استدعاء الدالة بالأعلى، مثل totalLength = 17 + pythagoras(12,5);، فإنه سيُحاوِل تَحْصِيل قيمة تَعْليمَة الاستدعاء pythagoras(12,5)، لذلك سيبدأ بإِسْناد قيم المُعامِلات الفعليّة (actual parameters) المُمرَّرة، أيّ ١٢ و ٥، إلى المُعامِلات الصُّوريّة (formal parameters) x و y بتعريف الدالة، ثم سينتقل إلى تَّنْفيذ مَتْن الدالة (function body)، وبالتالي سيضطرّ إلى تَحْصِيل قيمة التعبير Math.sqrt(12.0*12.0 + 5.0*5.0) والتي تؤول إلى القيمة 13.0، ثم سيُعيد هذه القيمة لتَكُون القيمة المُعادة (return value) من الدالة، وعليه تُستبدَل القيمة 13.0 بتَعْليمَة استدعاء الدالة، فتصبح تَعْليمَة الإِسْناد وكأنها مَكْتُوبة على الصورة totalLength = 17+13.0. أخيرًا، تُضاف القيمة المُعادة (return value) إلى العدد 17 ثم تُخْزَن النتيجة 30.0 بالمُتَغيِّر totalLength. لاحظ أنه وبينما تُنفَّذ الدالة، فإنك قد تَتَمكَّن من تَحْصِيل القيمة المُفْترَض إعادتها منها، وفي تلك الحالة، تستطيع اِستخدَام تَعْليمَة return؛ لإعادة تلك القيمة مباشرة، ومِنْ ثَمَّ تَخَطِّي أية تَعْليمَات آخرى بمَتْن الدالة، دون الحاجة إلى الانتظار إلى نهاية المَتْن، أيّ أنه ليس من الضروري أن تَكُون تَعْليمَة return هي آخر تَعْليمَة بمَتْن الدالة، بل يُمكِن اِستخدَامها بأي مكان ضِمْن المَتْن. ولكن، لابُدّ دائمًا من إعادة قيمة ما من الدالة وذلك بجميع مسارات التَّنْفيذ المُحتمَلة التي قد تَتَّخذها الدالة. كما ذَكَرَنا مُسْبَّقًا، لابُدّ دائمًا من كتابة تَعْليمَة return مَتبوعة بتعبير (expression) ضِمْن تعريف أي دالة (function)؛ لأن الدوال دائمًا ما تُعيد قيمة. في المقابل، لا يُعدّ ذلك ضروريًا مع البرامج الفرعية من غير الدوال (non-function)، أي تلك التي تَستخدِم void ضِمْن تعريفها للتَّصْريح عن نوعها المُعاد، فهي في النهاية لا تُعيد قيمة، ومع ذلك يَظِلّ اِستخدَام تَعْليمَة return ضِمْن هذا السِّياق مُمكنًا، بحيث يَقْتصِر دورها على إنهاء تَّنْفيذ البرنامج الفرعي بمكان ما وسط المَتْن وإعادة التَحكُّم (return control) مرة آخرى إلى سَطْر الشيفرة المسئول عن استدعاء البرنامج الفرعي. تُكتَب تَعْليمَة return في تلك الحالة على الصورة return; بدون تعبير (expression). بالإضافة إلى ما سبق، قد تُستخدَم تَعْليمَة return أيضًا داخل حَلْقة تَكْرار (loop) لإنهاء كُلًا من الحَلْقة (loop) والبرنامج الفرعي الحاضن لها. بالمثل، قد تُستخدَم بتَعْليمَة switch، للخروج من كُلًا من تَعْليمَة switch والبرنامج الفرعي الحاضن لها، أيّ ستَجِدْ أحيانًا تَعْليمَة return مُستخدَمة ضِمْن سِّياقات اعْتَدْت فيها استخدام تَعْليمَة break. أمثلة على الدوال (functions) تَعرَّضنا لمسألة حِسَاب قيم عناصر مُتتالية الأعداد"3N+1" عدة مرات بما في ذلك القسم السابق. يُمكِننا تعريف دالة (function) بسيطة جدًا، تَستقبِل قيمة العنصر الحالي بالمُتتالية، ثم تُعيد قيمة عنصر المتتالية التالي. اُنظر الشيفرة التالية: static int nextN(int currentN) { if (currentN % 2 == 1) // إذا كان currentN فرديًا return 3*currentN + 1; // أعد تلك القيمة else return currentN / 2; // إذا لم يكن، أعد تلك القيمة } نُلاحِظ أن تعريف الدالة بالأعلى يَتضمَّن تَعْليمَتي return. ستُنفَّذ دائمًا تَعْليمَة return واحدة فقط بِغَضّ النظر عن عدد تلك التَعْليمَات المُستخدَمة ضِمْن التعريف. يُفضِّل بعض المبرمجين كتابة تَعْليمَة return وحيدة بنهاية الدالة، ويحاولوا القيام بذلك قدر الإمكان؛ خاصة وأن ذلك يُسهِل من العثور على تَعْليمَة return ضِمْن التعريف. فمثلًا، يُمكِننا إعادة كتابة الدالة nextN() -المُعرَّفة بالأعلى- مرة آخرى على النحو التالي: static int nextN(int currentN) { int answer; // متغير يحمل القيمة المعادة من الدالة if (currentN % 2 == 1) // إذا كان currentN فرديًا answer = 3*currentN+1; // أسند القيمة إلى المتغير else answer = currentN / 2; // إذا لم يكن، أسند القيمة إلى المتغير return answer; // لا تنسى أن تعيد المتغير } دَعْنَا الآن نَكتُب برنامجًا فرعيًا (subroutine) لحِسَاب قيم عناصر المُتتالية، لكن بالاعتماد على الدالة nextN هذه المرة. نظرًا لكَوْن الدالة nextN قصيرة نوعًا ما، فإن التَحْسِينات المُجراة على البرنامج الفرعي ليست ذا شأن بالموازنة مع نسخة البرنامج بالقسم ٤.٣. في المقابل، إذا كانت الدالة nextN() طويلة لكَوْنها مثلًا تَحسِب عملية معقدة، فعندها سيَكُون من المنطقي إخفاء ذلك التعقيد بداخل دالة منفصلة: static void print3NSequence(int startingValue) { int N; // أحد عناصر المتتالية int count; // عدد عناصر المتتالية N = startingValue; // أول قيمة بالمتتالية count = 1; System.out.println("The 3N+1 sequence starting from " + N); System.out.println(); System.out.println(N); while (N > 1) { // احسب قيمة عنصر المتتالية التالي باستدعاء الدالة nextN N = nextN( N ); count++; // أزد عدد عناصر المتتالية بمقدار الواحد System.out.println(N); // اطبع قيمة العنصر } System.out.println(); System.out.println("There were " + count + " terms in the sequence."); } اُنظر المثال التالي للدالة letterGrade، والتي تَستقبِل درجة عددية (numerical grade)، وتُحوِّلها إلى نظيرتها المحرفية (letter grade) بالاستعانة بمقياس درجات (grading scale) تقليدي: /** * أعد الدرجة المحرفية المناظرة للدرجة العددية الممررة للدالة كمعامل */ static char letterGrade(int numGrade) { if (numGrade >= 90) return 'A'; // 90 or above gets an A else if (numGrade >= 80) return 'B'; // 80 to 89 gets a B else if (numGrade >= 65) return 'C'; // 65 to 79 gets a C else if (numGrade >= 50) return 'D'; // 50 to 64 gets a D else return 'F'; } // نهاية الدالة letterGrade تستطيع الدوال أن تُعيد قيم من أي نوع، فمثلًا، نوع القيمة المُعادة (return type) من الدالة letterGrade() -بالأعلى- هو النوع char، أما الدالة المُعرَّفة بالمثال التالي، فإنها تُعيد قيمة (return value) من النوع boolean. اُنظر الشيفرة التالية، واحْرِصْ على قراءة التعليقات (comments)؛ لأنها تُوضِح بعض النقاط المثيرة للاهتمام: /** * تعيد هذه الدالة القيمة المنطقية true إذا كان N عدد أولي. * العدد الأولي هو عدد صحيح أكبر من العدد 1 * بشرط ألا يكون قابلًا للقسمة إلا على نفسه وعلى الواحد. * Nإذا كان لدى N أي قاسم D يتراوح بين الواحد والعدد * فسيكون لديه قاسم يتراوح بين العدد 2 والجذر التربيعي للعدد N * لذلك سوف نختبر قيم القواسم المحتملة بداية من العدد 2 * وحتى الجذر التربيعي للعدد N */ static boolean isPrime(int N) { int divisor; // عدد يحتمل أن يكون قاسما للعدد if (N <= 1) return false; // العدد الأولي لابد أن يكون أكبر من الواحد int maxToTry; // أكبر قيمة قاسم محتمل ينبغي اختبارها // ينبغي إجراء عملية التحويل بين الأنواع لأن Math.sqrt // تعيد قيمة من النوع double maxToTry = (int)Math.sqrt(N); // سنحاول قسمة N على الأعداد من 2 ووصولا الى قيمة maxToTry // إذا لم يكن N قابل للقسمة على أي عدد من تلك الأعداد // سيكون N عددا اوليا for (divisor = 2; divisor <= maxToTry; divisor++) { if ( N % divisor == 0 ) // إذا تمكن divisor من تقسم N return false; // إذًا N لا يمكن أن يكون عددًا أوليًا // لا حاجة للاستمرار } // إذا وصلنا إلى هنا، فإن N حتما هو عدد أولي // لأنه إذا لم يكن أوليًا، ستكون الدالة بالفعل قد انتهت // باستخدام تعليمة return الموجودة بالحلقة السابقة return true; // نعم N هو عدد أولي } // نهاية الدالة isPrime نَسْتَعْرِض بالشيفرة التالية دالة آخرى، والتي تَستقبِل مُعامِل (parameter) من النوع String، وتُعيد قيمة (return value) من نفس النوع String. تَعْكِس تلك الدالة ترتيب محارف السِلسِلة النصية المُمرَّرة، وتُعيد نسخة مَعْكوسة منها، فمثلًا، النسخة المَعْكوسة من السِلسِلة النصية "Hello World" هي "dlroW olleH". يُمكِن توصيف الخوارزمية (algorithm) المطلوبة لعَكْسَ سِلسِلة نصية str على النحو التالي: ابدأ بسِلسِلة نصية فارغة (empty string)، ثم أَضِف إلى نهايتها كل محرف (character) موجود بالسِلسِلة النصية str، بحيث تبدأ بالمحرف الأخير من السِلسِلة str، وتعود للوراء حتى تَصِل إلى المحرف الأول. اُنظر الشيفرة التالية: static String reverse(String str) { String copy; // النسخة المعكوسة // الموضع الحالي من السلسلة النصية الممرة // يأخذ القيم بداية من طول السلسلة وحتى صفر int i; copy = ""; // ابدأ بسلسلة نصية فارغة for ( i = str.length() - 1; i >= 0; i-- ) { // أضف المحرف الحالي إلي نهاية النسخة المعكوسة copy = copy + str.charAt(i); } return copy; } لاحِظ أن بعض السَلاسِل النصية لا تتغَيَّر سواء قُرأت طردًا (forwards) أو عكسًا (backwards)، أي سواء قرأت من اليسار إلى اليمين أو العَكْس. يُعرَف ذلك باسم السِّياق المُتَناظِر أو القلب المستو (palindrome)، والذي يُمكِنك اختبار حُدُوثه على سِلسِلة نصية معينة word بالاستعانة بالدالة المُعرَّفة بالأعلى وباِستخدَام الاختبار if (word.equals(reverse(word))) تحديدًا. يقع كثير من المبتدئين، أثناء كتابتهم للدوال، بخطأ طباعة الإجابة بدلًا من إعادتها، وهو ما يُمثِل سوء فهم تام لماهية الدوال. إن مُهِمّة الدالة (function) ببساطة هو مجرد حِسَاب قيمة ما، ثم العودة مرة آخرى إلى سَطْر الشيفرة المسئول عن اِستدعاء الدالة، حيث ينبغي اِستخدَام القيمة المُعادة. قد يَطبَع ذلك السَطْر من الشيفرة تلك القيمة أو يُسنِدها إلى مُتَغيِّر أو يَستخدِمها ضِمْن تعبير (expression)، أو أي شيء آخر قد يَتطلَّبه البرنامج، المهم هنا هو أن تُدرِك أن أيًا كانت الطريقة التي سيُوظِّف بها المُستدعِي تلك القيمة المُعادة فهي بالنهاية أمر لا يَخُصّ الدالة. مسألة متتالية "3N+1" سنُحاوِل بالمثال الأخير من هذا القسم اِستخدَام الدالة nextN() التي عَرَفناها بالأعلى لتَعْديل البرنامج المسئول عن حِسَاب قيم عناصر المتتالية "3N+1". أُضيفَت بعض التَعْديلات الآخرى أيضًا، مثل طباعة قيم عناصر المتتالية بأعمدة، بحيث يَحتوِي كل سَطْر على ٥ عناصر فقط، مما سيُحسِن من هيئة الخَرْج الناتج عن البرنامج. تستطيع القيام بذلك ببساطة عن طريق الاحتفاظ بعدد العناصر المَطبوعة بالسَطْر الحالي، وبدء سَطْر خَرْج جديد عند وصول قيمة ذلك العدد إلى القيمة ٥. نُسِق الخَرْج بحيث تُصطَفَّ العناصر بأعمدة مُرتَّبة: input textio.TextIO; /** * برنامج يحسب قيم عناصر المتتالية 3N+1 ويطبعها. تُحدد قيمة * أول عنصر بالمتتالية من قبل المستخدم، ثم تُطبع القيم بأعمدة * بحيث يحتوي كل سطر على 5 عناصر فقط. * كما تطبع عدد عناصر المتتالية بالنهاية */ public class ThreeN2 { public static void main(String[] args) { System.out.println("This program will print out 3N+1 sequences"); System.out.println("for starting values that you specify."); System.out.println(); int K; // اقرأ مدخل المستخدم do { System.out.println("Enter a starting value;"); System.out.print("To end the program, enter 0: "); K = TextIO.getlnInt(); // اقرأ قيمة أول عنصر بالمتتالية if (K > 0) print3NSequence(K); // اعد تكرار الحلقة إذا كان k أكبر من الصفر } while (K > 0); } // نهاية main /** * يطبع print3NSequence قيم المتتالية 3N+1 إلى الخرج القياسي، * بحيث يكون أول عنصر بالمتتالية هو قيمة المعامل الممررة * كما يطبع أيضًا عدد عناصر المتتالية * قيمة المعامل startingValue الممررة ينبغي أن تكون عددًا صحيحا موجبا */ static void print3NSequence(int startingValue) { int N; // أحد عناصر المتتالية int count; // عدد العناصر حتى الآن int onLine; // عدد العناصر المطبوعة بالسطر الحالي N = startingValue; // ابدأ المتتالية بقيمة المعامل الممررة count = 1; // لدينا عنصر واحد بالمتتالية System.out.println("The 3N+1 sequence starting from " + N); System.out.println(); System.out.printf("%8d", N); onLine = 1; // هناك عدد مطبوع بالسطر الآن while (N > 1) { N = nextN(N); // احسب قيمة العنصر التالي count++; // أزد عناصر المتتالية بمقدار الواحد if (onLine == 5) { System.out.println(); // اطبع محرف العودة الى بداية السطر onLine = 0; // أعد ضبط عدد العناصر المطبوعة بالسطر } System.out.printf("%8d", N); // اطبع قيمة العنصر الحالي onLine++; // أزد قيمة عدد العنصر بالسطر بمقدار الواحد } System.out.println(); // انهي السطر الحالي من الخرج System.out.println(); // اطبع سطر فارغ System.out.println("There were " + count + " terms in the sequence."); } // نهاية print3NSequence /** * تحسب الدالة nextN قيمة عنصر المتتالية التالي وتعيده * بحيث تستقبل قيمة عنصر المتتالية الحالي كمعامل currentN */ static int nextN(int currentN) { if (currentN % 2 == 1) return 3 * currentN + 1; else return currentN / 2; } // نهاية الدالة nextN } // نهاية الصنف ThreeN2 ينبغي أن تَقْرأ هذا البرنامج بتأنِّي وأن تُحاوِل اِستيعاب طريقة عَمَله. ترجمة -بتصرّف- للقسم Section 4: Return Values من فصل Chapter 4: Programming in the Large I: Subroutines من كتاب Introduction to Programming Using Java.1 نقطة