تمرين من المصدر الأصلي · تطبيق عملي

قيصر

استخدم الشرح العربي للفهم، واعتمد صفحة CS50 الأصلية مرجعًا نهائيًا للمواصفات والتسليم.

جلسة دراسة مقترحة: حتى 60 دقيقةالجهد الكلي التقريبي: 120 دقيقة · أكمل من حيث توقفت لاحقًا.Week 2
محاور هذه الخطوة
  • المصفوفات
  • السلاسل النصية
  • سطر الأوامر
  • النطاق

ترجمة آلية تحت المراجعة

قيصر

المصدر الأصلي

قائمة تنفيذ التمرين

تُحفظ علامات هذه القائمة على هذا الجهاز والمتصفح فقط. أنشئ حسابًا مفعّلًا لحفظ إنجاز التمرين في مسارك.

قيصر

Caesar Cipher

مشكلة بحاجة إلى حل

من المفترض أن قيصر (نعم، قيصر) استخدم "تشفير" (i.e.، إخفاء بطريقة عكسية) الرسائل السرية عن طريق نقل كل حرف فيها بعدد من الأماكن. على سبيل المثال، قد يكتب A كـ B، وB كـ C، وC كـ D، ...، ويلتف حول الترتيب الأبجدي، Z كـ A. وهكذا، لكتابة HELLO لشخصٍ ما، قد يكتب قيصر IFMMP بدلًا منها. عند تلقي مثل هذه الرسائل من قيصر، سيتعين على المستلمين "فك تشفيرها" عن طريق نقل الحروف في الاتجاه المعاكس بنفس عدد الأماكن.

اعتمدت سرية "نظام التشفير" هذا على معرفة قيصر والمستلمين فقط بالسر، وهو عدد الأماكن التي قام قيصر بنقل رسائله إليها (e.g.، 1). ليس آمنًا بشكل خاص وفقًا للمعايير الحديثة، ولكن، إذا كنت ربما الأول في العالم الذي يفعل ذلك، فهو آمن تمامًا!

يتم استدعاء النص غير المشفر بشكل عام نص عادي. يسمى النص المشفر بشكل عام النص المشفر. والسر المستخدم يسمى أ مفتاح .

لكي نكون واضحين، إليك كيفية التشفير HELLO بمفتاح \(1\) العائد IFMMP:

نص عادي H E L L O
+ مفتاح \(1\) \(1\) \(1\) \(1\) \(1\)
= نص مشفر I F M M P

بشكل أكثر رسمية، تقوم خوارزمية قيصر (i.e., cipher) بتشفير الرسائل عن طريق "تدوير" كل حرف في مواضع \(k\). بشكل أكثر رسمية، إذا كان \(p\) نصًا عاديًا (i.e.، رسالة غير مشفرة)، \(p_i\) هو الحرف \(i^{th}\) في \(p\)، و\(k\) هو مفتاح سري (i.e.، عدد صحيح غير سالب)، فإن كل حرف، \(c_i\)، في النص المشفر، \(c\)، يكون تحسب كما

\[c_i = (p_i + k)\space\%\space26\]

حيث \(\%\space26\) هنا تعني "الباقي عند القسمة على 26." ربما تجعل هذه الصيغة التشفير يبدو أكثر تعقيدًا مما هو عليه بالفعل، ولكنها في الحقيقة مجرد طريقة مختصرة للتعبير عن الخوارزمية بدقة. في الواقع، من أجل المناقشة، فكر في A (أو a) كـ \(0\)، B (أو b) كـ \(1\)، ...، H (أو h) كـ \(7\)، I (أو i) كـ \(8\)، ...، و Z (أو z) كـ \(25\). لنفترض أن قيصر يريد فقط أن يقول Hi لشخص يستخدم بشكل سري، هذه المرة، المفتاح، \(k\)، من 3. وبالتالي فإن النص العادي، \(p\)، هو Hi، وفي هذه الحالة يكون الحرف الأول للنص العادي، \(p_0\)، هو H (المعروف أيضًا باسم 7)، والحرف الثاني في النص العادي، \(p_1\)، هو i (ويعرف أيضًا باسم 8). الحرف الأول في النص المشفر الخاص به، \(c_0\)، هو هكذا K، والحرف الثاني في النص المشفر، \(c_1\)، هو بالتالي L. منطقي؟

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

تجريبي

المواصفات

تصميم وتنفيذ برنامج، caesar، الذي يقوم بتشفير الرسائل باستخدام تشفير قيصر.

  • قم بتنفيذ برنامجك في ملف يسمى caesar.c في دليل يسمى caesar.
  • يجب أن يقبل برنامجك وسيطة سطر أوامر واحدة، وهو عدد صحيح غير سالب. دعونا نسميها \(k\) من أجل المناقشة.
  • إذا تم تنفيذ برنامجك بدون أي وسيطات سطر أوامر أو باستخدام أكثر من وسيطة سطر أوامر، فيجب أن يقوم برنامجك بطباعة رسالة خطأ من اختيارك (مع printf) والعودة من main قيمة 1 (والذي يميل إلى الإشارة إلى خطأ) على الفور.
  • إذا لم يكن أي من أحرف وسيطة سطر الأوامر رقمًا عشريًا، فيجب أن يقوم برنامجك بطباعة الرسالة Usage: ./caesar key والعودة من main قيمة 1.
  • لا تفترض أن \(k\) سيكون أقل من أو يساوي 26. يجب أن يعمل برنامجك مع كافة القيم التكاملية غير السالبة لـ \(k\) أقل من \(2^{31} - 26\). بمعنى آخر، لا داعي للقلق إذا تعطل برنامجك في النهاية إذا اختار المستخدم قيمة لـ \(k\) تكون كبيرة جدًا أو كبيرة جدًا بحيث لا يمكن احتواؤها في int. (أذكر أن أ int يمكن أن يتجاوز.) ولكن، حتى لو كان \(k\) أكبر من \(26\)، فإن الأحرف الأبجدية في إدخال برنامجك يجب أن تظل أحرفًا أبجدية في إخراج برنامجك. على سبيل المثال، إذا كان \(k\) يساوي \(27\)، A لا ينبغي أن يصبح \ بالرغم من ذلك \ يبعد \(27\) موضعًا عن A في ASCII، لكل asciitable.com; A يجب أن يصبح Bمنذ ذلك الحين B يبعد \(27\) موضعًا عن A، بشرط أن تلتف من Z إلى A.
  • يجب أن يقوم برنامجك بالإخراج plaintext: (مع مسافتين ولكن بدون سطر جديد) ثم قم بمطالبة المستخدم بـ string من النص العادي (باستخدام get_string).
  • يجب أن يقوم برنامجك بالإخراج ciphertext: (بمسافة واحدة ولكن بدون سطر جديد) متبوعًا بالنص المشفر المقابل للنص العادي، مع "تدوير" كل حرف أبجدي في النص العادي بمقدار k المواضع؛ يجب أن يتم إخراج الأحرف غير الأبجدية دون تغيير.
  • يجب أن يحافظ برنامجك على حالة الأحرف: يجب أن تظل الأحرف الكبيرة، على الرغم من تدويرها، أحرفًا كبيرة؛ يجب أن تظل الأحرف الصغيرة، على الرغم من تدويرها، أحرفًا صغيرة.
  • بعد إخراج النص المشفر، يجب عليك طباعة سطر جديد. يجب أن يخرج برنامجك بعد ذلك بالعودة 0 من main.

نصيحة

كيف تبدأ؟ دعونا نتعامل مع هذه المشكلة خطوة بخطوة.

الشيفرة شبه البرمجية

اكتب أولاً، حاول كتابة أ وظيفة main في caesar.c الذي ينفذ البرنامج باستخدام الشيفرة الكاذب فقط، حتى لو لم يكن (حتى الآن!) متأكدًا من كيفية كتابته بالشيفرة الفعلي.

تلميح

هناك أكثر من طريقة للقيام بذلك، إليك طريقة واحدة فقط!

int main(int argc, string argv[])
{
    // Make sure program was run with just one command-line argument

    // Make sure every character in argv[1] is a digit

    // Convert argv[1] from a `string` to an `int`

    // Prompt user for plaintext

    // For each character in the plaintext:

        // Rotate the character if it's a letter
}

لا بأس بتعديل الشيفرة الكاذب الخاص بك بعد رؤية الشيفرة الخاص بنا هنا، ولكن لا تقم ببساطة بنسخ/لصق الشيفرة الخاص بنا في الشيفرة الخاص بك!

حساب وسيطات سطر الأوامر

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

على وجه التحديد، قم بالتعديل main في caesar.c بطريقة بحيث، إذا لم يقدم المستخدم وسيطات سطر الأوامر، أو اثنين أو أكثر، تتم طباعة الوظيفة "Usage: ./caesar key\n" ثم يعود 1، الخروج من البرنامج بشكل فعال. إذا قام المستخدم بتوفير وسيطة سطر أوامر واحدة بالضبط، فيجب على البرنامج عدم طباعة أي شيء والعودة ببساطة 0. وبالتالي يجب أن يتصرف البرنامج وفقًا لما يلي.

$ ./caesar
Usage: ./caesar key
$ ./caesar 1 2 3
Usage: ./caesar key
$ ./caesar 1
تلميحات
  • تذكر أنه يمكنك الطباعة باستخدام printf.
  • تذكر أن الدالة يمكنها إرجاع قيمة باستخدام return.
  • تذكر ذلك argc يحتوي على عدد وسائط سطر الأوامر التي تم تمريرها إلى البرنامج، بالإضافة إلى اسم البرنامج.

التحقق من المفتاح

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

أضف إلى caesar.c، أدناه main، دالة تسمى، e.g.، only_digits الذي يستغرق string كوسيطة وإرجاع true إذا كان ذلك string يحتوي على أرقام فقط، 0 حتى 9، وإلا فسيتم إرجاعه false. تأكد من إضافة النموذج الأولي للوظيفة أعلاه main أيضًا.

تلميحات
  • من المحتمل أنك ستحتاج إلى نموذج أولي مثل:
    bool only_digits(string s);
    

    وتأكد من التضمين cs50.h أعلى ملفك، حتى يتعرف عليه المترجم stringbool).

  • تذكر أن أ string هو مجرد مصفوفة من charق.
  • تذكر ذلك strlen، تم الإعلان عنه في string.h، يحسب طول أ string.
  • قد تجد isdigit، تم الإعلان عنه في ctype.h، ليكون مفيدًا، لكل manual.cs50.io. لكن لاحظ أنه يتحقق فقط من واحد char في المرة الواحدة!

ثم قم بالتعديل main بطريقة يستدعيها only_digits على argv[1]. إذا عادت هذه الوظيفة falseإذن يجب طباعة main "Usage: ./caesar key\n" والعودة 1. آخر main يجب أن يعود ببساطة 0. وبالتالي يجب أن يتصرف البرنامج وفقًا لما يلي:

$ ./caesar 42
$ ./caesar banana
Usage: ./caesar key

استخدام المفتاح

الآن قم بالتعديل main بطريقة يتم تحويلها argv[1] إلى int. قد تجد atoi، تم الإعلان عنه في stdlib.h، ليكون مفيدًا، لكل manual.cs50.io. ثم استخدم get_string لمطالبة المستخدم ببعض النص العادي "plaintext: ".

بعد ذلك، قم بتنفيذ دالة تسمى، e.g., rotate، يستغرق ذلك أ char كمدخل وأيضًا int، ويقوم بتدوير ذلك char بهذا العدد من المواضع إذا كان حرفًا (i.e.، أبجديًا)، ملتفًا من Z إلى A (ومن z إلى a) حسب الحاجة. إذا char ليس حرفًا، ويجب أن تقوم الدالة بدلاً من ذلك بإرجاع نفس الحرف char لم يتغير.

تلميحات
  • من المحتمل أنك ستحتاج إلى نموذج أولي مثل:
    char rotate(char c, int n);
    

    استدعاء دالة مثل

    rotate('A', 1)
    

    أو حتى وبالتالي يجب أن يعود

    rotate('A', 27)
    

    'B'. واستدعاء وظيفة مثل يجب أن يعود

    rotate('!', 13)
    

    '!'.

  • تذكر أنه يمكنك "إلقاء" صراحةً char إلى int مع (int)و int إلى أ char مع (char). أو يمكنك القيام بذلك ضمنيًا ببساطة عن طريق التعامل مع أحدهما على أنه الآخر.
  • من المحتمل أنك ستحتاج إلى طرح قيمة ASCII الخاصة بـ 'A' من أي أحرف كبيرة، وذلك لعلاج 'A' ك 0, 'B' ك 1، وما إلى ذلك، أثناء إجراء العمليات الحسابية. ثم قم بإضافته مرة أخرى عند الانتهاء من نفس الشيء.
  • من المحتمل أنك ستحتاج إلى طرح قيمة ASCII الخاصة بـ 'a' من أي أحرف صغيرة، وذلك لعلاج 'a' ك 0, 'b' ك 1، وما إلى ذلك، أثناء إجراء العمليات الحسابية. ثم قم بإضافته مرة أخرى عند الانتهاء من نفس الشيء.
  • قد تجد بعض الوظائف الأخرى المعلنة فيها ctype.h ليكون مفيدًا لكل manual.cs50.io.
  • ستجد الاحتمالات % مفيد عند "الالتفاف" حسابيًا من قيمة مثل 25 إلى 0.

ثم قم بالتعديل main بطريقة تتم طباعتها "ciphertext: " ثم يتكرر على كل char في النص العادي للمستخدم، يتصل rotate على كل منها، وطباعة القيمة المرجعة لها.

تلميحات
  • تذكر ذلك يمكن printf طباعة أ char باستخدام %c.
  • إذا كنت لا ترى أي نتائج على الإطلاق عند الاتصال printf، من المحتمل أنك تطبع أحرفًا خارج نطاق ASCII الصالح من 0 إلى 127. حاول طباعة الأحرف مؤقتًا كأرقام (باستخدام %i بدلاً من %c) لمعرفة القيم التي تطبعها!

الإرشادات التفصيلية

محتوى خارجي من YouTube

لن نتصل بهذه الخدمة قبل موافقتك. يمكنك فتح المصدر في نافذة مستقلة بدل تحميله هنا.

فتح المصدر

كيفية الاختبار

صحة

check50 cs50/problems/2026/x/caesar
كيفية الاستخدام debug50

أتطلع للتشغيل debug50؟ يمكنك القيام بذلك على النحو التالي، بعد تجميع الشيفرة الخاصة بك بنجاح باستخدام make,

debug50 ./caesar KEY

حيث KEY هو المفتاح الذي تقدمه كوسيطة سطر أوامر لبرنامجك. لاحظ أن تشغيل

debug50 ./caesar

سيؤدي (من الناحية المثالية!) إلى إنهاء برنامجك عن طريق مطالبة المستخدم بمفتاح.

النمط

style50 caesar.c

كيفية الإرسال

في جهازك الطرفي، قم بتنفيذ ما يلي لإرسال عملك، والإجابة على المطالبات التي تظهر أيضًا.

submit50 cs50/problems/2026/x/caesar

أنهيت التطبيق؟

راجع عناصر التنفيذ أعلاه، ثم احفظ إنجازك وانتقل إلى التمرين التالي.

حفظ إنجاز التمرين

يمكنك قراءة الدورة كاملة دون حساب. يصبح حفظ التقدم متاحًا بعد تفعيل الحساب، ويبقى محفوظًا بعد انتهاء العضوية.

العودة إلى جميع التمارين