يشكّل هذا الدرس التطبيقي التدريبي جزءًا من الدورة التدريبية لبرمجة مطوّري البرامج في Kolin. ستحصل على أقصى قيمة ممكنة من هذه الدورة التدريبية إذا كنت تستخدم الدروس التطبيقية حول الترميز بشكل متسلسل. بناءً على معلوماتك، قد تتمكّن من تصفّح بعض الأقسام. هذه الدورة التدريبية موجّهة للمبرمجين الذين يعرفون لغة موجّهة نحو الأغراض ويريدون تعلّم لغة Kotlin.
مقدمة
في هذا الدرس التطبيقي، ستتعلّم أساسيات لغة البرمجة Kotlin: أنواع البيانات وعوامل التشغيل والمتغيّرات وهياكل التحكّم والمتغيّرات الخالية من القيم الفارغة. هذه الدورة التدريبية موجّهة للمبرمجين الذين يعرفون لغة موجّهة نحو الأغراض ويريدون تعلّم لغة Kotlin.
بدلاً من إنشاء نموذج تطبيق واحد، تم تصميم الدروس في هذه الدورة التدريبية لبناء معرفتك، ولكن تكون شبه مستقلة عن بعضها البعض ليتسنى لك تصفح الأقسام التي تعرفها. لربطهما معًا، تستخدم العديد من الأمثلة مظهر معرض أحواض السمك. وإذا أردت الاطّلاع على القصة الكاملة لأحواض السمك، يمكنك الاطّلاع على الدورة التدريبية بعنوان Kotlin Botcamp for Programmers Udacity.
ما يجب معرفته
- كيفية إنشاء مشروع من خلال IntelliJ IDEA
- كيفية فتح الرمز وتنفيذه في Kotlin's REPL (Read-Eval-Print Loop) في IntelliJ IDEA (Tools > Kotlin > Kotlin REPL)
ما ستتعرَّف عليه
- كيفية استخدام أنواع بيانات وعوامل تشغيل ومتغيرات Kotlin
- كيفية العمل مع المنطقيّة والشروط
- الفرق بين المتغيّرات الفارغة وغير القابلة للقيم الفارغة
- آلية عمل المصفوفات والقوائم والحلقات في Kotlin
الإجراءات التي ستنفذّها
- يمكنك التعاون مع فريق Kotlin REPL للتعرّف على أساسيات لغة Kotlin.
في هذه المهمة، ستتعرّف على معلومات عن عوامل التشغيل والأنواع في لغة البرمجة Kotlin.
الخطوة 1: استكشاف عوامل التشغيل الرقمية
- افتح IntelliJ IDEA، إذا لم يكن مفتوحًا حاليًا.
- لفتح Kotlin REPL، اختَر Tools (الأدوات) > Kotlin > Kotlin REPL. (
)
كما هو الحال مع اللغات الأخرى، يستخدم Kotlin +
و-
و*
و/
علامة الجمع والطرح والأوقات والتقسيم. ويتيح أيضًا لغة Kotlin أنواعًا مختلفة من الأرقام، مثل Int
وLong
وDouble
وFloat
.
- أدخِل التعبيرات التالية في REPL. للاطّلاع على النتيجة، اضغط على
Control+Enter
(Command+Enter
على جهاز Mac) بعد كل نتيجة.
1+1 ⇒ res8: kotlin.Int = 2 53-3 ⇒ res9: kotlin.Int = 50 50/10 ⇒ res10: kotlin.Int = 5 1.0/2.0 ⇒ res11: kotlin.Double = 0.5 2.0*3.5 ⇒ res12: kotlin.Double = 7.0
لاحظ أن نتائج العمليات تحتفظ بأنواع المعاملات، لذلك، 1/2 = 0، ولكن 1.0/2.0 = 0.5.
- جرّب بعض التعبيرات التي تحتوي على مجموعات مختلفة من الأرقام الصحيحة والأرقام العشرية.
6*50 ⇒ res13: kotlin.Int = 300 6.0*50.0 ⇒ res14: kotlin.Double = 300.0 6.0*50 ⇒ res15: kotlin.Double = 300.0
- اتّصِل ببعض الطرق حول الأرقام. يحافظ Kotlin على الأرقام كعناصر أولية، ولكن يسمح لك باستدعاء طرق على الأرقام كما لو كانت عناصر.
2.times(3) ⇒ res5: kotlin.Int = 6 3.5.plus(4) ⇒ res8: kotlin.Double = 7.5 2.4.div(2) ⇒ res9: kotlin.Double = 1.2
الخطوة 2: التدرّب على استخدام الأنواع
لا تحوّل لغة Kotlin ضمن أنواع الأرقام بشكل ضمني، لذا لا يمكنك تخصيص قيمة قصيرة مباشرةً إلى متغيّر طويل أو Byte
إلى Int
. ويرجع ذلك إلى أن الإحالة الناجحة للأرقام الضمنية هي مصدر شائع للأخطاء في البرامج. ويمكنك دائمًا تحديد قيم من أنواع مختلفة من خلال إرسال المحتوى.
- لعرض بعض النتائج المحتمَلة، اختَر متغيّرًا من النوع
Int
في REPL.
val i: Int = 6
- أنشئ متغيّرًا جديدًا، ثم أدخِل اسم المتغيّر الموضّح أعلاه، متبوعًا بالرقم
.to
.
val b1 = i.to
يعرض IntelliJ IDEA قائمة بالإكمالات الممكنة. تعمل ميزة الإكمال التلقائي هذه مع المتغيرات والكائنات من أي نوع.
- اختَر
toByte()
من القائمة، ثم اطبع المتغير.
val b1 = i.toByte()
println(b1)
⇒ 6
- حدِّد قيمة
Byte
لمتغيّرات أنواع مختلفة.
val b2: Byte = 1 // OK, literals are checked statically
println(b2)
⇒ 1
val i1: Int = b2
⇒ error: type mismatch: inferred type is Byte but Int was expected
val i2: String = b2
⇒ error: type mismatch: inferred type is Byte but String was expected
val i3: Double = b2
⇒ error: type mismatch: inferred type is Byte but Double was expected
- بالنسبة إلى المهام التي عرضت أخطاءً، جرِّب إرسالها بدلاً من ذلك.
val i4: Int = b2.toInt() // OK!
println(i4)
⇒ 1
val i5: String = b2.toString()
println(i5)
⇒ 1
val i6: Double = b2.toDouble()
println(i6)
⇒ 1.0
- لتسهيل الثوابت الرقمية الطويلة، تتيح لك لغة Kotlin وضع شرطات سفلية في الأرقام، حيث تكون هذه تناسبك. جرِّب إدخال ثوابت رقمية مختلفة.
val oneMillion = 1_000_000 val socialSecurityNumber = 999_99_9999L val hexBytes = 0xFF_EC_DE_5E val bytes = 0b11010010_01101001_10010100_10010010
الخطوة 3: التعرّف على قيمة أنواع المتغيّرات
تدعم لغة Kotlin نوعَين من المتغيّرات: وهما "قابل للتغيير" و"غير قابل للتغيير". باستخدام val
، يمكنك تحديد قيمة مرة واحدة. إذا حاولت تخصيص عنصر مرة أخرى، ستظهر لك رسالة خطأ. باستخدام var
، يمكنك تخصيص قيمة، ثم تغيير القيمة في وقت لاحق من البرنامج.
- حدِّد المتغيرات باستخدام
val
وvar
، ثم خصِّص قيمًا جديدة لها.
var fish = 1
fish = 2
val aquarium = 1
aquarium = 2
⇒ error: val cannot be reassigned
يمكنك تخصيص قيمة fish
، ثم تخصيص قيمة جديدة لها، لأنه يتم تحديدها باستخدام var
. وتؤدي محاولة تحديد قيمة جديدة إلى aquarium
إلى حدوث خطأ نظرًا لأنه تم تحديدها باستخدام val
.
يتم استنتاج النوع الذي تخزّنه في المتغيّر عندما تتمكّن أداة التجميع من فهمه من خلال السياق. يمكنك دائمًا تحديد نوع المتغيّر بشكل صريح باستخدام تدوين النقطتين الرأسيتين.
- حدِّد بعض المتغيرات وحدِّد النوع بشكل صريح.
var fish: Int = 12
var lakes: Double = 2.5
بعد تعيين النوع من خلالك أو من خلال برنامج التجميع، لا يمكنك تغيير النوع أو ستتلقى رسالة خطأ.
الخطوة 4: التعرّف على السلاسل
تعمل السلاسل في Kotlin إلى حد ما مثل السلاسل في أي لغة برمجة أخرى باستخدام "
للسلاسل و'
للأحرف الفردية، ويمكنك ربط السلاسل باستخدام عامل التشغيل +
. يمكنك إنشاء نماذج سلسلة من خلال دمجها مع القيم، ويتم استبدال اسم $
variable
بالنص الذي يمثل القيمة. يُسمّى هذا الإجراء الإدخال المتغيّر.
- أنشِئ نموذج سلسلة.
val numberOfFish = 5
val numberOfPlants = 12
"I have $numberOfFish fish" + " and $numberOfPlants plants"
⇒ res20: kotlin.String = I have 5 fish and 12 plants
- يمكنك إنشاء نموذج سلسلة يتضمن تعبيرًا فيه. وكما هو الحال مع اللغات الأخرى، يمكن أن تكون القيمة ناتجة عن تعبير. استخدِم الأقواس المعقوفة
{}
لتحديد التعبير.
"I have ${numberOfFish + numberOfPlants} fish and plants"
⇒ res21: kotlin.String = I have 17 fish and plants
في هذه المَهمّة، ستتعلّم معلومات عن القواعد المنطقية والتحقّق من صحة لغة البرمجة Kotlin. وكما هو الحال مع اللغات الأخرى، تشتمل لغة Kotlin على عوامل تشغيل منطقية و منطقية مثل أقل من أو تساوي أو أكبر من ذلك وما إلى ذلك (<
و==
و>
و!=
و<=
و>=
).
- اكتب عبارة
if
/else
.
val numberOfFish = 50
val numberOfPlants = 23
if (numberOfFish > numberOfPlants) {
println("Good ratio!")
} else {
println("Unhealthy ratio")
}
⇒ Good ratio!
- جرّب نطاقًا في عبارة
if
. في لغة Kotlin، يمكن للشرط الذي تختبره استخدام النطاقات أيضًا.
val fish = 50
if (fish in 1..100) {
println(fish)
}
⇒ 50
- اكتب
if
بعدة حالات. بالنسبة إلى الشروط الأكثر تعقيدًا، يمكنك استخدام المنطقي&&
و المنطقي أو||
. وكما هو الحال مع اللغات الأخرى، يمكنك استخدام عدة حالات باستخدامelse if
.
if (numberOfFish == 0) {
println("Empty tank")
} else if (numberOfFish < 40) {
println("Got fish!")
} else {
println("That's a lot of fish!")
}
⇒ That's a lot of fish!
- جرِّب عبارة
when
. تتوفّر طريقة أكثر جمالاً لكتابة هذه السلسلة من عباراتif
/else if
/else
باللغة Kotlin، باستخدام عبارةwhen
التي تشبه عبارةswitch
بلغات أخرى. يمكن للشروط في عبارةwhen
استخدام النطاقات أيضًا.
when (numberOfFish) {
0 -> println("Empty tank")
in 1..39 -> println("Got fish!")
else -> println("That's a lot of fish!")
}
⇒ That's a lot of fish!
في هذه المَهمّة، ستتعرّف على المزيد من المعلومات عن المتغيّرات القابلة للقيم المتغيّرة وغير المتغيّرة. كانت أخطاء البرمجة التي تتضمن قيمًا فارغة مصدرًا لعدد لا حصر له من الأخطاء. يسعى Kotlin للحد من الأخطاء عن طريق تقديم متغيّرات غير فارغة.
الخطوة 1: التعرّف على القيمة الفارغة
وفقًا للإعدادات التلقائية، لا يمكن أن تكون المتغيّرات null
.
- أعلن عن
Int
وخصصnull
له.
var rocks: Int = null
⇒ error: null can not be a value of a non-null type Int
- استخدِم عامل تشغيل علامة الاستفهام،
?
، بعد النوع للإشارة إلى أن المتغيّر يمكن أن يكون فارغًا. أعلن عنInt?
وخصصnull
له.
var marbles: Int? = null
عندما يكون لديك أنواع بيانات معقدة، مثل قائمة:
- يمكنك السماح بأن تكون عناصر القائمة فارغة.
- يمكنك السماح بأن تكون القائمة فارغة، ولكن إذا لم تكن القائمة فارغة، لا يمكن أن تكون عناصرها فارغة.
- يمكنك السماح بجعل كل من القائمة أو العناصر فارغة.
يتم تناول القوائم وبعض أنواع البيانات المعقدة الأخرى في مهمة لاحقة.
الخطوة الثانية: التعرّف على ؟ و?: عاملا التشغيل
يمكنك اختبار null
باستخدام عامل التشغيل ?
، ما يوفّر عليك عناء كتابة العديد من if
/else
.
- اكتب بعض الرموز بالطريقة الأطول للتحقق مما إذا كان المتغيّر
fishFoodTreats
ليسnull
. وبعد ذلك، قلِّل ذلك المتغير.
var fishFoodTreats = 6
if (fishFoodTreats != null) {
fishFoodTreats = fishFoodTreats.dec()
}
- يمكنك الآن البحث عن طريقة Kotlin للكتابة باستخدام عامل التشغيل
?
.
var fishFoodTreats = 6
fishFoodTreats = fishFoodTreats?.dec()
- ويمكنك أيضًا ربط الاختبارات الفارغة باستخدام عامل التشغيل
?:
. انظر إلى هذا المثال:
fishFoodTreats = fishFoodTreats?.dec() ?: 0
إنها مختزَلة مع &القيمة إذا لم تكن القيمة fishFoodTreats
null
، وتنقص وتستخدمها، وبخلاف ذلك استخدِم القيمة بعد ?:
، وهي 0." إذا كانت قيمة fishFoodTreats
هي null
، يتم إيقاف التقييم ولا يتم استدعاء الطريقة dec()
.
نقطة عن المؤشرات الفارغة
إذا كنت تحب تطبيق NullPointerExceptions
، يسمح لك Kotlin بالاحتفاظ به. تعمل عامل تأكيد العدم خالٍ، !!
(double-bang)، على تحويل أي قيمة إلى نوع غير فارغ وعرض استثناء باستثناء القيمة إذا كانت null
.
val len = s!!.length // throws NullPointerException if s is null
في هذه المهمة، يمكنك تعلّم المصفوفات والقوائم، وتعلّم طرقًا مختلفة لإنشاء حلقات بلغة البرمجة Kotlin.
الخطوة 1: إعداد القوائم
القوائم هي نوع أساسي في لغة Kotlin، وهي تشبه القوائم بلغات أخرى.
- إعلان قائمة باستخدام
listOf
وطباعتها. لا يمكن تغيير هذه القائمة.
val school = listOf("mackerel", "trout", "halibut")
println(school)
⇒ [mackerel, trout, halibut]
- الإعلان عن قائمة يمكن تغييرها باستخدام
mutableListOf
. إزالة عنصر
val myList = mutableListOf("tuna", "salmon", "shark")
myList.remove("shark")
⇒ res36: kotlin.Boolean = true
تعرض الطريقة remove()
القيمة true
عند إزالة العنصر الذي تم تمريره بنجاح.
الخطوة 2: إنشاء مصفوفات
مثل اللغات الأخرى، لغة Kotlin هي arays. وعلى عكس القوائم في لغة Kotlin التي تحتوي على إصدارات قابلة للتغيير وغير قابلة للتغيير، ليس هناك نسخة قابلة للتغيير من Array
. بعد إنشاء مصفوفة، يتم إصلاح المقاس. ولا يمكنك إضافة عناصر أو إزالتها، إلا عن طريق النسخ إلى مصفوفة جديدة.
تتماثل قواعد استخدام val
وvar
مع المصفوفات كما هو الحال مع القوائم.
- يُرجى تحديد مصفوفة من السلاسل باستخدام
arrayOf
. استخدم الأداة المساعدة للمصفوفةjava.util.Arrays.toString()
لطباعتها.
val school = arrayOf("shark", "salmon", "minnow")
println(java.util.Arrays.toString(school))
⇒ [shark, salmon, minnow]
- لا يحتوي مصفوفة تم الإعلان عنها مع
arrayOf
على نوع مرتبط بالعناصر، لذا يمكنك المزج بين الأنواع، وهو أمر مفيد. تحديد مصفوفة من أنواع مختلفة
val mix = arrayOf("fish", 2)
- ويمكنك أيضًا تعريف المصفوفات باستخدام نوع واحد لجميع العناصر. ويمكن تعريف مصفوفة من الأعداد الصحيحة باستخدام
intArrayOf()
. تتوفّر أدوات إنشاء أو وظائف إنشاء مثيلات خاصة بمصفوفات من أنواع أخرى.
val numbers = intArrayOf(1,2,3)
- ادمج مصفوفتين مع عامل التشغيل
+
.
val numbers = intArrayOf(1,2,3)
val numbers3 = intArrayOf(4,5,6)
val foo2 = numbers3 + numbers
println(foo2[5])
=> 3
- جرِّب مجموعات مختلفة من المصفوفات والقوائم المدمجة. وكما هو الحال في اللغات الأخرى، يمكنك دمج المصفوفات والقوائم. وهذا يعني أنك عندما تضع مصفوفة داخل مصفوفة، يكون لديك مصفوفة من المصفوفات، وليست مصفوفة مسطحة من محتوى الاثنين. يمكن أيضًا أن تكون عناصر المصفوفة هي قوائم، ويمكن أن تكون عناصر القوائم مصفوفات.
val numbers = intArrayOf(1, 2, 3)
val oceans = listOf("Atlantic", "Pacific")
val oddList = listOf(numbers, oceans, "salmon")
println(oddList)
⇒ [[I@89178b4, [Atlantic, Pacific], salmon]
العنصر الأول numbers
هو Array
. في حال عدم استخدام أداة مساعدة المصفوفة لطباعتها، يطبع Kotlin العنوان بدلاً من محتوى المصفوفة.
- هناك ميزة رائعة في لغة Kotlin وهي أنه يمكنك إعداد المصفوفات باستخدام الرمز بدلاً من إعدادها على 0. جرِّب المثال التالي:
val array = Array (5) { it * 2 }
println(java.util.Arrays.toString(array))
⇒ [0, 2, 4, 6, 8]
يقع رمز الإعداد بين الأقواس المعقوفة، {}
. في الرمز، يشير it
إلى فهرس المصفوفة الذي يبدأ بـ 0.
الخطوة 3: إنشاء تكرار
الآن بعد أن أصبحت لديك قوائم ومصفوفات، تعمل العناصر بشكل متكرر على النحو المتوقّع.
- إنشاء مصفوفة استخدِم حلقة
for
وتكرار المصفوفة وطباعتها.
val school = arrayOf("shark", "salmon", "minnow")
for (element in school) {
print(element + " ")
}
⇒ shark salmon minnow
- وفي لغة Kotlin، يمكنك التنقل بين العناصر والفهارس في الوقت نفسه. جرِّب المثال التالي:
for ((index, element) in school.withIndex()) {
println("Item at $index is $element\n")
}
⇒ Item at 0 is shark Item at 1 is salmon Item at 2 is minnow
- جرِّب أحجام ونطاقات مختلفة للخطوات. يمكنك تحديد نطاقات من الأرقام أو الأحرف، أبجديًا. وكما هو الحال مع اللغات الأخرى، ليس عليك التقدم بمقدار 1. يمكنك الرجوع إلى الصفحة السابقة باستخدام
downTo
.
for (i in 1..5) print(i)
⇒ 12345
for (i in 5 downTo 1) print(i)
⇒ 54321
for (i in 3..6 step 2) print(i)
⇒ 35
for (i in 'd'..'g') print (i)
⇒ defg
- جرّب بعض الحلقات. وكما هو الحال مع اللغات الأخرى، تشتمل لغة Kotlin على
while
تكرار وdo...while
تكرار وعوامل تشغيل++
و--
. لدى Kotlin أيضًاrepeat
تكرار.
var bubbles = 0
while (bubbles < 50) {
bubbles++
}
println("$bubbles bubbles in the water\n")
do {
bubbles--
} while (bubbles > 50)
println("$bubbles bubbles in the water\n")
repeat(2) {
println("A fish is swimming")
}
⇒ 50 bubbles in the water 49 bubbles in the water A fish is swimmingA fish is swimming
تشبه لغة Kotlin اللغات الأخرى إلى حد كبير عندما يتعلق الأمر بالأساسيات مثل عوامل التشغيل والقوائم والحلقات، ولكن هناك بعض الاختلافات المهمة.
قد تختلف الميزات التالية بلغة Kotlin عن تلك التي اعتدت عليها في اللغات الأخرى:
- لا يمكن تحويل أنواع لغة Kotlin بشكل ضمني: استخدم الإرسال.
- لا يمكن تخصيص المتغيرات المُعلنة عن طريق
val
إلا مرة واحدة. - لا تكون قيم متغيرات Kotlin فارغة تلقائيًا. استخدِم
?
لجعل المتغيّرات فارغة. - باستخدام Kotlin، يمكنك تكرار التنقل في الفهرس وعناصر المصفوفة في آنٍ واحد في تكرار
for
.
تشبه بنية لغة Kotlin التالية النصوص البرمجية بلغات أخرى:
- يمكن أن يكون للمصفوفات والقوائم نوع واحد أو أنواع مختلطة.
- يمكن دمج المصفوفات والقوائم.
- يمكنك إنشاء حلقات باستخدام
for
وwhile
وdo
/while
وrepeat
. - عبارة
when
هي إصدار Kotlin' من عبارةswitch
، ولكنwhen
أكثر مرونة.
مستندات Kotlin
إذا كنت بحاجة إلى مزيد من المعلومات عن أي موضوع في هذه الدورة التدريبية، أو إذا واجهتك مشكلة، يمكنك البدء باستخدام https://kotlinlang.org.
- تحويل نوع فاضح
- تعريف المتغيرات
- نماذج السلاسل
- قيم غير قيمة
- القوائم
- المصفوفات
if
وwhen
وfor
وwhile
- عامل تشغيل
?:
(Elvis) - عامل التشغيل
!!
برامج تعليمية بلغة Kotlin
يتضمّن الموقع الإلكتروني https://try.kotlinlang.org برامج تعليمية غنية اسمها Kotlin Koans ومترجمًا فوريًا مستندًا إلى الويب ومجموعة كاملة من المستندات المرجعية التي تتضمّن أمثلة.
دورة Udacity التدريبية
للاطّلاع على دورة Udacity التدريبية حول هذا الموضوع، يُرجى الاطّلاع على برنامج Kotlin Campus للمبرمجين.
IntelliJ IDEA
يمكن العثور على مستندات IntelliJ IDEA على الموقع الإلكتروني JetBrains.
يسرد هذا القسم المهام الدراسية المحتملة للطلاب الذين يعملون من خلال هذا الدرس التطبيقي حول الترميز في إطار دورة تدريبية يُديرها معلِّم. يجب أن ينفِّذ المعلّم ما يلي:
- يمكنك تخصيص واجب منزلي إذا لزم الأمر.
- التواصل مع الطلاب بشأن كيفية إرسال الواجبات المنزلية
- وضع درجات للواجبات المنزلية.
ويمكن للمعلّمين استخدام هذه الاقتراحات بقدر ما يريدون أو بقدر ما يريدون، ويجب عدم التردد في تخصيص أي واجبات منزلية أخرى مناسبة.
إذا كنت تستخدم هذا الدرس التطبيقي بنفسك، يمكنك استخدام هذه الواجبات المنزلية لاختبار معلوماتك.
الإجابة عن هذه الأسئلة
السؤال 1
أي مما يلي يعلن قائمة سلاسل غير قابلة للتغيير؟
▢ val school = arrayOf("shark", "salmon", "minnow")
▢ var school = arrayOf("shark", "salmon", "minnow")
▢ val school = listOf("shark", "salmon", "minnow")
▢ val school = mutableListOf("shark", "salmon", "minnow")
السؤال 2
ما نتيجة الرمز التالي؟for (i in 3..8 step 2) print(i)
▢ 345678
▢ 468
▢ 38
▢ 357
السؤال 3
ما الغرض من استخدام علامة الاستفهام في هذا الرمز؟var rocks: Int? = 3
▢ نوع المتغيّر rocks
غير ثابت.
▢ يمكن ضبط المتغيّر rocks
على قيمة فارغة.
▢ لا يمكن ضبط المتغير rocks
على قيمة فارغة.
▢ يجب عدم ضبط المتغير rocks
على الفور.
انتقل إلى الدرس التالي:
للحصول على نظرة عامة على الدورة التدريبية، بما في ذلك الروابط إلى مختبرات ترميز أخرى، يُرجى الاطّلاع على "Kotlin Botcamp للمبرمجين: مرحبًا بك في الدورة التدريبية&"