كتاب "جافا للجميع"

صورة مرحبا ، habrozhiteli! هذا الكتاب مخصص للمبتدئين.


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


المصفوفات


المصفوفة تعني مجموعة من نفس النوع من القيم (المتغيرات) ، والتي يمكن الوصول إليها من خلال اسم شائع. هذا الفصل مخصص للصفائف.


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


صفائف أحادية البعد


من المريح تخيل صفيف أحادي البعد في شكل سلسلة مرتبة أو سلسلة متغيرات (من نفس النوع). لإعلان صفيف أحادي البعد ، يجب عليك تحديد النوع الذي تنتمي إليه عناصر المصفوفة واسم المصفوفة وأيضًا عدد العناصر المضمنة في المصفوفة. بناء جملة التصريح عن صفيف أحادي البعد هو:


[] =new []; 

أولاً ، تتم الإشارة إلى نوع عناصر الصفيف ، وبعد معرف النوع يتبعه أقواس مربعة فارغة. بعد ذلك ، اسم المصفوفة ، عامل التخصيص ، التعليمة (المشغل) جديدة ، مرة أخرى نوع عناصر المصفوفة وفي أقواس مربعة حجم المصفوفة (عدد العناصر في المصفوفة). على سبيل المثال ، يُعلن الأمر int nums = new int [20] عن صفيف عدد صحيح مكون من 20 عنصرًا.


بالمعنى الدقيق للكلمة ، فإن أمر تعريف الصفيف المقدم هنا هو تعايش بين أمرين: أسماء الأوامر int [] التي تعلن عن عددًا متغيرًا من النوع "integer array" (متغير صفيف) والتعليم الجديد int [20] ، والذي ينشئ الصفيف في الواقع. يتم تعيين هذه التعليمات بقيمة متغير nums ، ونتيجة لمرجع الصفيف ، يتم كتابتها إلى متغير nums. بمعنى آخر ، يمكن تنفيذ عملية إنشاء صفيف بأمرين:


 int[] nums; nums=new int[20]; 

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


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

في الحالات التي لا يوجد فيها سوء فهم ، سنقوم بتعريف متغير الصفيف بالصفيف.


عند التصريح عن متغير الصفيف ، يُسمح بالإشارة إلى الأقواس المربعة إما بعد معرف النوع أو بعد اسم الصفيف. على سبيل المثال ، بدلاً من الأمر int [] nums ، يمكنك استخدام الأمر int nums [].


يتم الوصول إلى عنصر الصفيف أحادي البعد من خلال اسم المصفوفة مع فهرس العنصر المشار إليه بين قوسين معقوفين. تبدأ عناصر مجموعة الفهرسة من الصفر. وبالتالي ، سيبدو المرجع إلى العنصر الأول من صفيف nums مثل nums [0]. إذا كان هناك 20 عنصرًا في المصفوفة ، فإن العنصر الأخير من المصفوفة يحتوي على فهرس يتكون من 19 عنصرًا ، أي أن التعليمات للوصول إلى العنصر تبدو مثل nums [19].


يمكن العثور على طول المصفوفة باستخدام خاصية الطول: يشار إلى اسم المصفوفة ، ومن خلال النقطة ، خاصية الطول. على سبيل المثال ، لمعرفة طول صفيف nums ، يمكنك استخدام عبارة nums.length. ثم يمكن كتابة المرجع إلى العنصر الأخير من الصفيف كـ nums [nums.length-1].


إشعار يستخدم Java تحققًا تلقائيًا لمعرفة ما إذا كان الخروج من الصفيف أم لا. لذلك ، إذا أشار الرمز إلى عنصر صفيف غير موجود ، يحدث خطأ.

عند التصريح عن صفيف ، يتم تخصيص الذاكرة لذلك. في Java ، تتم تهيئة عناصر الصفيف تلقائيًا بقيم صفرية - الخلايا المحددة صفرية ، ويتم تفسير قيم هذه الخلايا الصفرية وفقًا لنوع الصفيف. لكن يجب ألا تعتمد على هذا التهيئة التلقائية. من الحكمة تهيئة عناصر الصفيف بشكل صريح. للقيام بذلك ، استخدم عامل التشغيل loop أو حدد قائمة بقيم العناصر عند تعريف صفيف.


لتهيئة المصفوفة بقائمة من القيم عند التصريح عن متغير المصفوفة ، بعد أن يشار إليها (من خلال مشغل المهمة) بقائمة من القيم المحاطة بأقواس. على سبيل المثال:


 int[] data={3,8,1,7}; 

هنا ، يتم الإعلان عن البيانات المتغيرة لصفيف عدد صحيح ، ويتم إنشاء مجموعة ، وكتابة رابط إلى هذا المتغير. يتم تحديد حجم المصفوفة وقيم العناصر تلقائيًا وفقًا لعدد العناصر في قائمة القيم. في هذه الحالة ، يتم إنشاء صفيف صحيح من أربعة عناصر بقيم العناصر 3 و 8 و 1 و 7. ويمكن تحقيق نفس النتيجة باستخدام ، على سبيل المثال ، الأوامر التالية:


 int[] data; data=new int[]{3,8,1,7}; 

يعلن الأمر int [] الأول من البيانات عن متغير صفيف. ينشئ التعليمة الجديدة int [] {3،8،1،7} صفيفًا من أربعة أعداد صحيحة ، ويتم تعيين مرجع إلى هذا الصفيف إلى البيانات المتغيرة (بمعنى أن بيانات الأوامر = int الجديدة [] {3،8،1،7}) .
يتم عرض مثال للإعلان عن الصفائف وتهيئتها واستخدامها في القائمة 3.1.


قائمة 3.1. تقديم صفائف أحادية البعد


 class Demo{ public static void main(String[] args){ //  : int i,n; //   : int[] data; //  : data=new int[]{3,8,1,7}; //  : n=data.length; //  : int[] nums=new int[n]; //   : for(i=0;i<nums.length;i++){ nums[i]=2*data[i]-3; System.out.println("nums["+i+"]="+nums[i]); } } } 

يقوم البرنامج بالإعلان عن تهيئة مجموعة بيانات من أربعة عناصر وتهيئتها. يتم حساب طول الصفيف بواسطة عبارة data.length. تتم كتابة هذه القيمة إلى متغير عدد صحيح n (الأمر n = data.length). بعد ذلك ، يُعلن الأمر int [] nums = new int [n] عن صفيف أعداد صحيح آخر. يتم تحديد عدد العناصر في هذه المصفوفة حسب قيمة المتغير n ، وبالتالي ، فإنه يتزامن مع حجم صفيف البيانات. يتم ملء صفيف nums باستخدام عبارة حلقة. يتم احتساب قيم عناصر صفيف nums بناءً على قيم عناصر صفيف البيانات (num command [i] = 2 * data [i] -3). لعرض قيم عناصر صفيف nums ، استخدم الأمر System.out.println ("nums [" + i + "] =" + nums [i]).


فيما يلي الشكل الذي تبدو عليه نتيجة تنفيذ البرنامج:
نتيجة البرنامج (من القائمة 3.1)


 nums[0]=3 nums[1]=13 nums[2]=-1 nums[3]=11 

مرة أخرى ، نلاحظ أن فهرسة عناصر الصفيف يبدأ من الصفر. لذلك ، في مشغل الحلقة ، يتم تهيئة متغير الفهرس i بقيمة الصفر الأولية ، ويتم استخدام عامل عدم المساواة الصارم في حالة الاختبار التي تبلغ <nums.length.
من المهم أيضًا أنه عند إنشاء مصفوفة nums ، يتم تحديد حجمها باستخدام المتغير n ، والتي يتم حساب قيمتها أثناء تنفيذ البرنامج.


صفائف ثنائية الأبعاد ومتعددة الأبعاد


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


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

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


 [][] =new [][]; 

كما في حالة صفيف أحادي البعد ، يمكن تقسيم هذا الأمر إلى قسمين:


 [][] ; =new [][]; 

يعلن الأمر الأول عن متغير لصفيف ثنائي الأبعاد. ينشئ الأمر الثاني صفيفًا ثنائي الأبعاد فعليًا بالأحجام المحددة ، ويتم تعيين مرجع إلى هذا الصفيف كقيمة لمتغير الصفيف. على سبيل المثال ، يقوم الأمر double [] [] data = new double [3] [4] بإنشاء صفيف ثنائي الأبعاد مع عناصر من النوع المزدوج. يحتوي الصفيف على 3 صفوف و 4 أعمدة ، ويتم كتابة الرابط إلى الصفيف إلى متغير البيانات. نفس الأوامر سوف تؤدي إلى نفس النتيجة:


 double[][] data; data=new double[3][4]; 

يتم الوصول إلى عناصر المصفوفة ثنائية الأبعاد بالتنسيق التالي: يشار إلى اسم المصفوفة ، أما الأقواس المربعة فهي الفهرس الأول للعنصر ، أما الأقواس المربعة الأخرى فتكون الفهرس الثاني لعنصر المصفوفة. تبدأ الفهرسة عبر جميع الأبعاد من الصفر. على سبيل المثال ، ارتباط البيانات [0] [3] عبارة عن استدعاء لعنصر من صفيف البيانات مع الفهارس 0 و 3 ، وهو عنصر في الصف الأول والعمود الرابع.


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


 double[][] data={{0.1,0.2,0.3},{0.4,0.5,0.6}}; int nums[][]={{1,2,3},{4,5}}; 

يقوم الأمر الأول بإنشاء وتهيئة صفيف بيانات ثنائي الأبعاد 2 في 3 (صفين وثلاثة أعمدة). يتم تحديد عدد الصفوف حسب عدد العناصر في القائمة الخارجية. هناك عنصرين من هذا القبيل - هذه هي القوائم {0.1،0.2،0.3} و {0.4،0.5،0.6}. كل من هذه القوائم لديها ثلاثة عناصر. من هنا نحصل على مجموعة من الحجم 2 في 3. القائمة {0.1،0.2،0.3} تحدد قيم العناصر في السطر الأول ، والقائمة {0.4،0.5،0.6} تحدد قيم العناصر في السطر الثاني. على سبيل المثال ، يحصل عنصر البيانات [0] [0] على القيمة 0.1 ، ويحصل عنصر البيانات [0] [2] على القيمة 0.3 ، ويحصل عنصر البيانات [1] [0] على القيمة 0.4 ، ويحصل عنصر البيانات [1] [2] على القيمة 0.6 .


ينشئ الأمر الثاني صفيفًا رقمًا صحيحًا يتكون من سطرين (نظرًا لوجود عنصرين داخل القائمة المخصصة - يسرد {1،2،3} و {4،5}). ومع ذلك ، يحتوي السطر الأول من الصفيف الذي تم إنشاؤه على ثلاثة عناصر (نظرًا لوجود ثلاث قيم في القائمة {1،2،3}) ، ويحتوي السطر الثاني من الصفيف على عنصرين (نظرًا لوجود قيمتين في القائمة {4،5}). في الصفيف الذي تم إنشاؤه ، يكون للعنصر [0] [0] القيمة 1 ، والعنصر [0] [1] له القيمة 2 ، والعنصر [0] [2] له القيمة 3 ، والعنصر [1] [0] له القيمة 4 وتكون عناصر العنصر [1] [1] هي القيمة 5.


تفاصيل المشكلة هي أن الصفيف nums في خطوط مختلفة يحتوي على عدد مختلف من العناصر ، لا. من الناحية الفنية ، يتم تنفيذ كل شيء أكثر من مجرد. يشير متغير nums الصفيف ثنائي الأبعاد فعلياً إلى صفيف أحادي الأبعاد مكون من عنصرين (عدد الصفوف في صفيف ثنائي الأبعاد). لكن عناصر هذه المصفوفة ليست أعدادًا صحيحة ، ولكن المتغيرات التي يمكن أن تشير إلى صفيف أعداد صحيحة أحادية البعد (تحدث نسبيًا ، تكون العناصر من النوع int []). يشير المتغير الأول إلى صفيف أحادي الأبعاد يتكون من ثلاثة عناصر (1 و 2 و 3) ، ويشير المتغير الثاني إلى صفيف أحادي الأبعاد يتكون من ثلاثة عناصر (4 و 5). عندما نقوم بفهرسة (مع فهرس واحد!) متغير nums ، يمكننا الوصول إلى عنصر الصفيف أحادي الأبعاد المشار إليه بواسطة هذا المتغير. على سبيل المثال ، nums [0] هو العنصر الأول ، و nums [1] هو العنصر الثاني للصفيف المذكور من متغيرات الصفيف. وهذه العناصر هي إشارة إلى المصفوفات. يمكن فهرستها. لذلك ، دعنا نقول nums [0] [1] هو العنصر الثاني في الصفيف الذي يشير إليه العنصر الأول [0] في الصفيف الذي يشير إليه متغير nums. لذلك كل شيء يحدث حقا. ونفسر تعليمات nums [0] [1] على أنها نداء لعنصر في الصف الأول وفي العمود الثاني من صفيف ثنائي الأبعاد.

يُظهر سرد 3.2 مثالاً عن برنامج يقوم بإنشاء صفيف ثنائي الأبعاد يتم ملؤه باستخدام عبارات حلقة متداخلة.


القائمة 3.2. إنشاء مجموعة ثنائية الأبعاد


 class Demo{ public static void main(String[] args){ int i,j,n=3,val=1; //   : int[][] nums=new int[n-1][n]; //   : for(i=0;i<n-1;i++){ //    for(j=0;j<n;j++){ //    //   : nums[i][j]=val++; //   : System.out.print(nums[i][j]+" "); } //    : System.out.println(); } } } 

ينشئ الأمر int [] [] nums = new [n-1] [n] new عددًا صحيحًا للأعداد مع صفوف n-1 وأعمدة n. يتم تعيين القيمة 3 للمتغير n مسبقًا. يتم تعبئة الصفيف باستخدام عوامل تشغيل حلقة متداخلة. يتم تعيين القيمة إلى عنصر الصفيف (لمؤشرات معينة i و j) من خلال num الأمر [i] [j] = val ++. هنا ، يتم تعيين عنصر nums [i] [j] القيمة الحالية للقيمة val ، وبعد ذلك مباشرة يتم زيادة قيمة val val بمقدار واحد.


إشعار نتيجة لتنفيذ تعليمة val ++ ، يتم زيادة قيمة val المتغيرة بمقدار واحد. ولكن نظرًا لاستخدام نموذج postfix لعامل الزيادة ، فإن قيمة تعبير val ++ هي القيمة القديمة (قبل الزيادة بواحد) للقيمة val المتغيرة.

بعد حساب قيمة العنصر ، يتم عرضه في منطقة الإخراج. نتيجة لهذا البرنامج ، نحصل على:
نتيجة البرنامج (من القائمة 3.2)


 1 2 3 4 5 6 

تُظهر القائمة 3.3 رمز البرنامج الذي يُنشئ مصفوفة ثنائية الأبعاد مع سلاسل بأطوال مختلفة.


القائمة 3.3. صفيف مع سلاسل بأطوال مختلفة


 class Demo{ public static void main(String[] args){ int i,j,val=1; //   (   ): int[][] nums=new int[4][]; //     : for(i=0;i<nums.length;i++){ //    : nums[i]=new int[i+1]; } //  : for(i=0;i<nums.length;i++){ for(j=0;j<nums[i].length;j++){ //   : nums[i][j]=val++; //   : System.out.print(nums[i][j]+" "); } //    : System.out.println(); } } } 

ينشئ الأمر int [] [] nums = new int [4] [] صفيف عدد صحيح ثنائي الأبعاد. هذه المجموعة تتكون من أربعة صفوف. ومع ذلك ، لم يتم تحديد حجم الخطوط - الزوج الثاني من الأقواس المربعة في نهاية الأمر فارغ. بتعبير أدق ، لا توجد خطوط حتى الآن. يجب أن يتم إنشاؤها. يتم إنشاء خطوط في بيان حلقة. في عبارة الحلقة هذه ، يعمل متغير الفهرس i من خلال القيم من 0 إلى nums.length-1 ، شاملة. ينشئ الأمر nums [i] = new int [i + 1] صفًا من صفيف ثنائي الأبعاد مع فهرس i. هذه السلسلة تحتوي على عنصر i + 1.


التفاصيل من الناحية الفنية ، يحدث كل شيء كما يلي: تنشئ التعليمة int [i + 1] الجديدة صفيفًا أحاديًا الأبعاد (سلسلة لصفيف ثنائي الأبعاد) ويتم كتابة مرجع إلى هذه الصفيف إلى متغير nums [i]. في هذه الحالة ، يمكن تفسير الأرقام [i] على أنها رابط لسطر مع الفهرس i. سوف يصبح معنى تعليمة nums.length واضحًا إذا كنت تتذكر أن المصفوفة ثنائية الأبعاد هي في الواقع صفيف أحادي البعد ، تشير عناصره إلى صفائف أحادية البعد. في هذه الحالة ، يعطي nums.length قيمة لعدد العناصر في الصفيف المشار إليها بواسطة متغير nums - وهذا هو ، عدد الصفوف في الصفيف ثنائي الأبعاد.

نتيجة لذلك ، نحصل على صفيف ثنائي الأبعاد من النوع الثلاثي: في السطر الأول من الصفيف يوجد عنصر واحد ، في الثاني - عنصرين ، وهكذا ، حتى السطر الرابع من الصفيف.
يتم تعبئة الصفيف الذي تم إنشاؤه باستخدام عوامل حلقة متداخلة. يأخذ متغير الفهرس i في مشغل الحلقة الخارجية القيم من 0 إلى nums.length-1 ويحدد صفًا في مصفوفة nums ثنائية الأبعاد. بالنسبة لمتغير الفهرس j ، يتم تحديد الحد الأعلى لنطاق التباين بواسطة تعليمة nums [i] .length ، والتي تُرجع عدد العناصر في الصف مع الفهرس i (يتغير المتغير j من 0 إلى nums [i] .length-1 ضمناً).


التفاصيل تجدر الإشارة إلى أن الأرقام [i] ، في الواقع ، عبارة عن متغير يشير إلى صفيف أحادي البعد يشكل سلسلة مع الفهرس i. يتم تحديد عدد العناصر في هذه المجموعة (السلسلة) حسب التعبير nums [i] .length.

يتم تعيين قيمة عناصر الصفيف بواسطة الأمر nums [i] [j] = val ++. فيما يلي الشكل الذي تبدو عليه نتيجة تنفيذ البرنامج:
نتيجة البرنامج (من القائمة 3.3)


 1 2 3 4 5 6 7 8 9 10 

كما هو متوقع ، حصلنا على مجموعة تحتوي على أعداد مختلفة من العناصر في صفوف مختلفة.


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

»يمكن الاطلاع على مزيد من المعلومات حول الكتاب على موقع الناشر
» المحتويات
» مقتطفات


خصم 25٪ على كوبون للباعة المتجولين - جافا


عند دفع النسخة الورقية من الكتاب ، يتم إرسال كتاب إلكتروني عبر البريد الإلكتروني.

Source: https://habr.com/ru/post/ar472076/


All Articles