الاسبوع التانى cs50

 جدول المحتويات

الأسبوع 2

آخر مرة

  • قدمنا ​​البرمجة باستخدام Scratch ، وفي الأسبوع الماضي تعلمنا القليل من لغة C ، وهي لغة نصية.

  • لقد تمت طباعة برنامجنا الأول البسيط للتو hello, world:

    #include <stdio.h>

    int main(void)
    {
    printf("hello, world\n");
    }
    • ولكن كان هناك الكثير من السحر ، ومن خلال تقديم Scratch أولاً ، تمكنا من تعلم المفاهيم المهمة أولاً.

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

  • تعلمنا أيضًا استخدام CS50 IDE.

  • ومفاهيم مثل:

    • المهام

    • الحلقات

    • المتغيرات

    • التعبيرات المنطقية

    • الظروف

  • والوظائف في لغة C مثل printf، ولكن في الحقيقة كانت لها تشابهات في Scratch ، مثل sayالكتلة الأرجواني .

  • تعلمنا تضمين مكتبة CS50 ، مع وظائف مفيدة مثل:

    • get_char

    • get_double

    • get_float

    • get_int

    • get_long_long

    • get_string

    • ...

  • وأنواع البيانات مثل:

    • bool

    • char

    • double

    • float

    • int

    • long long

    • string

    • ...

  • واجهنا أيضًا بعض حدود الحوسبة ، مثل تجاوز عدد صحيح أو عدم دقة تعويم ، والتي يمكن أن يكون لها تأثير شديد في العالم الحقيقي.

تصحيح

  • بشكل عام ، تأتي الأخطاء من المترجم ، makeأو clangكما سنوضح:

    int main(void)
    {
    printf("hello, world\n");
    }
    • عندما نحاول تجميع هذا فقط ، نحصل على خطأ يشبه:

      ~/workspace/ $ make buggy0
      clang -ggdb3 -O0 -std=c11 -Wall -Werror -Wshadow buggy0.c -lcs50 -lm -o buggy0
      buggy0.c:3:5: error: implicitly declaring library function 'printf' with type 'int (const char *, ...)' [-Werror]
      printf("hello, world\n");
      ^
      buggy0.c:3:5: note: include the header <stdio.h> or explicitly provide a declaration for 'printf'
      1 error generated.
      make: *** [buggy0] Error 1
    • السطر الأول الذي يبدأ بأمرنا clangأمر طبيعي ، وما makeيفعله لنا تمامًا.

    • يخبرنا السطر التالي ، في الملف buggy0.c، السطر 3، الحرف 5، أن هناك خطأ. نحن نعلن ضمنيًا أن وظيفة المكتبة "printf" ... وقد لا نفهم الرسالة بأكملها أو كل جزء منها ، لكن يمكننا تخمين ما تعنيه الأجزاء. يشبه إعلان الدالة ذكرها أو تعريفها ، والتذكر في المرة الأخيرة التي اضطررنا فيها إلى إعلان النموذج الأولي للوظيفة قبل أن نتمكن من استخدامها.

    • نظرًا لأننا لم نكتب printf، فلا يجب أن نحاول كتابته بأنفسنا ، بل يجب علينا تضمين ملف المكتبة الذي يحتوي على التطبيق ، #include <stdio.h>في السطر الأول. (في الواقع ، يخبرنا السطر الأخير من الخطأ بنفس القدر!)

  • الآن إذا كنا makeملفنا الجديد ، يجب أن يعمل كل شيء.

    #include <stdio.h>

    int main(void)
    {
    printf("hello, world\n");
    }
  • قدمت لك مجموعة المشكلات الأولى help50، أمرًا كتبه الموظفون سيساعد في شرح بعض الأخطاء التي ما زلت لا تفهمها.

  • لنجرب الآن برنامج عربات التي تجرها الدواب آخر:

    #include <stdio.h>

    int main(void)
    {
    string s = get_string();
    printf("hello, %s\n", s);
    }
    • يا فتى ، makeأسفرت محاولة ذلك 7 errors generatedلكن التمرير لأعلى قليلاً وإصلاح الأول أولاً هو فكرة جيدة بشكل عام. نحن نرى:

      buggy0.c:5:5: error: use of undeclared identifier 'string'; did you mean 'stdin'?
      string s = get_string();
      ^~~~~~
      stdin
    • لقد قصدنا بالتأكيد أن نقول string، فلماذا يكون المترجم مشوشًا بعض الشيء؟ حسنا، stringو get_string()كلاهما يأتي من مكتبة CS50، لذلك نحن بحاجة إلى #includeذلك أيضا.

  • لنحاول الآن طباعة #10 مرات:

    #include <stdio.h>

    int main(void)
    {
    for (int i = 0; i <= 10; i++)
    {
    printf("#\n");
    }
    }
    • تذكر أن forحلقة مثل هذه تعين بعض المتغيرات على عدد البداية ، وتتحقق لمعرفة ما إذا كنا قد وصلنا إلى عدد التكرارات ، ثم تقوم بتشغيل الكود داخلها وتضيفها إلى العداد.

  • يمكننا تجميع هذا دون أي مشاكل ، ولكن عند تشغيله ، نرى 11 #رمزًا ، وليس 10 كما أردنا.

  • لذلك دعونا eprintfنقوم بتضمين وظيفة جديدة من مكتبة CS50 ، والتي تطبع الأخطاء (أو أي شيء نريد تمييزه على أنه خاص) على الشاشة:

    #include <cs50.h>
    #include <stdio.h>

    int main(void)
    {
    for (int i = 0; i <= 10; i++)
    {
    printf("#\n");
    eprintf("i is now %i\n", i);
    }
    }
  • الآن نرى مجموعة من المدخلات:

    ~/workspace/ $ ./buggy0
    #
    buggy0:buggy0.c:9: i is now 0
    #
    buggy0:buggy0.c:9: i is now 1
    #
    buggy0:buggy0.c:9: i is now 2
    #
    buggy0:buggy0.c:9: i is now 3
    #
    buggy0:buggy0.c:9: i is now 4
    #
    buggy0:buggy0.c:9: i is now 5
    #
    buggy0:buggy0.c:9: i is now 6
    #
    buggy0:buggy0.c:9: i is now 7
    #
    buggy0:buggy0.c:9: i is now 8
    #
    buggy0:buggy0.c:9: i is now 9
    #
    buggy0:buggy0.c:9: i is now 10
    • لذا يبدو أنه eprintfيخبرنا باسم برنامجنا ، واسم الملف الذي ينتمي إليه ، ورقم السطر الموجود عليه.

    • حسنًا ، بين 0 و 10 ، يوجد في الواقع 11 رقمًا منذ أن بدأنا من 0. لذا يمكننا تغيير الحلقة للتوقف قبل الرقم 10 i < 10أو البدء بها int i = 1لكن تقليديًا ، نحب أن نبدأ العد من أقل رقم ، 0 ، ونتوقف قبل أن نصل إلى عدد التكرارات التي نريدها.

    • حتى الآن يمكننا إصلاح خطأنا ، وإزالة eprintf، واستدعاء هذا البرنامج.

  • لنجرب واحدة أخرى:

    #include <cs50.h>
    #include <stdio.h>

    int get_negative_int();

    int main(void)
    {
    int i = get_negative_int();
    printf("%i is a negative integer\n", i);
    }

    int get_negative_int(void)
    {
    int n;
    do
    {
    printf("n is ");
    n = get_int();
    }
    while (n > 0);
    return n;
    }
    • يحدث الكثير هنا ، لكن يمكننا معرفة ذلك. في الداخل get_negative_int، نقوم بإنشاء متغير جديد n، ونحصل على int من المستخدم أثناء n > 0ثم نعيده.

    • get_negative_int()يوجد voidداخل أقواسها ، نظرًا لأنها لا تأخذ أي وسيطات ، ولكن لها intمقدمة ، لأن هذا هو نوع البيانات التي نسترجعها منها.

    • يقوم برنامجنا بالتجميع ، لذلك ربما يكون لدينا خطأ منطقي. لنقم بتشغيله ونجرب بعض الأرقام:

      ~/workspace/ $ ./buggy3
      n is 1
      n is 2
      n is 3
      n is 50
      n is -50
      -50 is a negative integer
      ~/workspace/ $ ./buggy3
      n is -1
      -1 is a negative integer
      ~/workspace/ $ ./buggy3
      n is -2
      -2 is a negative integer
      ~/workspace/ $ ./buggy3
      n is 0
      0 is a negative integer
    • حسنًا ، يبدو أنه يعمل باستثناء تلك الحالة الأخيرة. حسنًا ، يمكننا إلقاء نظرة على الكود الخاص بنا لمحاولة اكتشاف مكان حدوث هذا الخطأ ، ولكن بمجرد أن يصبح برنامجنا أكثر تعقيدًا ، سنحتاج إلى بعض الأدوات الأفضل أيضًا.

    • يمكننا استخدام أداة CS50 أخرى تسمى debug50، والتي تتيح لنا تشغيل برنامجنا خطوة بخطوة ، وسطرًا بسطر ، وإلقاء نظرة على ما يحدث.

  • هيا نركض:

    ~/workspace/ $ debug50 ./buggy3
    n is
    • ظهرت لوحة على اليمين ، مع حدوث الكثير:

      التصحيح 50
  • ولكن إذا كتبنا رقمًا لبرنامجنا ، فيبدو أنه يفعل الشيء الطبيعي ويخبرنا أنه خارج:

    ~/workspace/ $ debug50 ./buggy3
    n is -1
    -1 is a negative integer

    Child exited with status 0
    GDBserver exiting

    ~/workspace/ $
    • هذا يعني حقًا أن برنامجنا قد انتهى وبالتالي سيتوقف المصحح عن العمل أيضًا.

  • لذلك يمكننا النقر فوق المساحة الإضافية بجوار السطر ، وستظهر نقطة حمراء تخبر مصحح الأخطاء بإيقاف البرنامج مؤقتًا:

    نقطة توقف
  • الآن بمجرد تشغيل الأمر نفسه ، سنتوقف عند هذا السطر وسيخبرنا المصحح عن متغيراتنا:

    المتغيرات
  • لننقر الآن على الزر الموجود بجوار المثلث الأزرق في الأعلى (الذي يقرأ Step Overإذا قمت بالتمرير فوقه) ، والذي يدير هذا الخط.

  • والآن سنرى في المحطة الطرفية الموجه ، وإذا -1كتبنا الموجه مرة أخرى ، فسنرى أن المتغير الخاص بنا قد تغير في مصحح الأخطاء. سنرى أيضًا السطر الموجود أسفله Call Stackفي مصحح الأخطاء ، وإذا ضغطنا على الزر "خطوة أخرى" مرة أخرى ، فسنرى ما تم -1طباعته. أخيرًا ، إذا ضغطنا على خطوة واحدة أخيرة (على قوس النهاية mainفي برنامجنا) ، فإن كل شيء مكتمل ويخرج كما كان من قبل.

  • لنقم بتشغيله مرة أخرى بنفس الأمر ، ولكن هذه المرة بدلاً من النقر Step Over، سننقر فوق الزر المجاور له (الذي يشبه سهمًا يشير لأسفل) ، Step Intoوالوظيفة.

  • وفجأة ، سننتقل إلى العبارة الأولى (يعلن الآخرون عن المتغيرات والبناء ، لكن لا يفعلون أي شيء) في get_negative_intوظيفتنا:

    الدخول في وظيفة
    • سنضع رقمًا ، 0ونضغط Step Overلأن السطر التالي هو فقط get_intالذي نعرف أنه يعمل.

    • لكننا الآن في السطر الأخير return n;، بدلاً من داخل الحلقة كما نريد. (تذكر أن الهدف من كل هذا هو الحصول على رقم سالب!)

    • لذلك نرى أن المتغير لدينا nهو 0، والشرط كان while (n > 0).

    • بما 0أنه ليس أكبر من 0، فإن الحلقة لا تستمر ، ولا يُطلب منا رقمًا آخر.

    • لذلك كل ما نحتاجه هو تغيير الحالة إلى while (n >= 0)، والآن يجب أن يعمل برنامجنا بشكل صحيح.

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

  • وإذا لم ينجح ذلك ، فإن CS50 لديها فريق دعم كامل جاهز للمساعدة!

أشياء يجب معرفتها

  • يتم تقييم مجموعات مشكلات CS50 على 4 محاور ، ونطاق ، وصحة ، وتصميم ، ونمط.

    • النطاق هو مقدار مجموعة المشكلات التي حاولت القيام بها.

    • الصواب هو ما إذا كان برنامجك يعمل كما ينبغي.

    • التصميم هو مدى جودة كتابة برنامجك ، بناءً على صفات مثل الكفاءة والشفرة المكررة ، إلخ.

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

  • سيكون لكل من هذه المحاور درجة تتراوح من 1 إلى 5 ، ولا داعي للقلق بشأن الدرجات من 3 ، مع بعض 2 و 4 ، في البداية ، حيث سيكون لديك مساحة كبيرة للنمو والتحسين خلال الفصل الدراسي .

  • يتم ترجيح المحاور أيضًا بالصيغة التالية:

    • النطاق × (صحة × 3 + تصميم × 2 + نمط × 1)

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

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

  • يسعدنا أن نسمح لزملائك في الفصل ، و TFs ، والأصدقاء بمساعدتك ، ولكن "... عند طلب المساعدة ، يمكنك إظهار الكود الخاص بك للآخرين ، ولكن لا يمكنك عرض الكود الخاص بهم ..."

  • هذا موضح أكثر في المنهج الدراسي ، وإذا كان لا يزال لديك أسئلة ، فيرجى التواصل مع ديفيد أو رؤساء فرق العمل لدينا للحصول على إرشادات!

  • لدينا أيضًا بند نادم ، "إذا ارتكبت فعلًا غير معقول ولكنك استرعت انتباه رؤساء الدورة إليه خلال 72 ساعة ، فقد تفرض الدورة عقوبات محلية قد تتضمن درجة غير مرضية أو فاشلة للعمل المقدم ، ولكن ولن تحيل الدورة الأمر لمزيد من الإجراءات التأديبية إلا في حالات تكرار الأفعال ".

التشفير

  • نحن الآن نتعمق في أول تطبيق لنا في العالم الحقيقي ، أو التصوير بالتبريد ، أو القدرة على إرسال واستقبال الرسائل السرية (المشفرة).

  • نشاهد مقطعًا قصيرًا من فيلم A Christmas Story ، حيث يقوم الطفل Ralphie بفك تشفير رسالة سرية من الراديو بحماس بحلقة ترسم الحروف إلى أحرف أخرى ، فقط ليجد أنها مجرد إعلان عن Ovaltine ، مشروب شعبية منذ سنوات عديدة.

  • يمكن تصنيف طريقة من هذا القبيل على أنها تشفير المفتاح السري ، حيث يعرف المرسل والمستقبل بعض القيمة السرية أو الشفرة أو العبارة التي يمكن استخدامها لتشفير المعلومات وفك تشفيرها.

  • يمكن أيضًا اختزال التشفير إلى خوارزمية تأخذ المدخلات وتنتج المخرجات.

  • المدخلات هي المفتاح والنص العادي ، أو الرسالة في شكل غير مشفر يمكن لأي شخص قراءتها ، والمخرجات هي النص المشفر ، أو الرسالة المشفرة التي لا يمكن فك تشفيرها أو فك تشفيرها إلا من لديه المفتاح.

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

  • إذا أردنا تخزين اسم زاميلا ، فسنضع Zamylaكل شخصية في صندوق:

    -------------------------
    | Z | a | m | y | l | a |
    -------------------------
  • وهذا أمر مهم خدمة الزبائن Beacause نريد تغيير حرف واحد في وقت واحد، ويقول Aل Bو Bل C.

  • يمكننا أن نرى هذا في العمل مع البرنامج التالي:

    #include <cs50.h>
    #include <stdio.h>
    #include <string.h>

    int main(void)
    {
    string s = get_string();
    for (int i = 0; i < strlen(s); i++)
    {
    printf("%c\n", s[i]);
    }
    }
    • أولاً ، نقوم بتضمين مكتبة جديدة string.h، تتضمن وظائف تساعدنا في العمل مع السلاسل. ثم نحصل على سلسلة sمن المستخدم ، وطول السلسلة ، strlen(s)نطبع حرفًا ، أيًا كان s[i]و s[i]هو مجرد تدوين للحصول على كل ما هو في هذا الموقف من صفيف. لذا ستبدأ الحلقة بـ iset to 0، بمعنى أننا نحصل s[0]على الحرف الأول في السلسلة ، ثم s[1]، ثم s[2]، وهكذا ، حتى تتم طباعة كل حرف في السلسلة:

      Z
      a
      m
      y
      l
      a
  • ولكن ماذا يحدث إذا كتب المستخدم ، على سبيل المثال ، سلسلة طويلة حقًا أو فعل شيئًا يتسبب في حدوث خطأ؟ حسنًا ، get_string()وبعض الوظائف الأخرى في لغة C ، يمكنها إرجاع قيمة خاصة تسمى NULLوذلك لتكون آمنة، انها ممارسة جيدة للتأكد من أن sليس NULLقبل أن تحاول أن تفعل شيئا معها:

    #include <cs50.h>
    #include <stdio.h>
    #include <string.h>

    int main(void)
    {
    string s = get_string();
    if (s != NULL)
    {
    for (int i = 0; i < strlen(s); i++)
    {
    printf("%c\n", s[i]);
    }
    }
    }
    • !=تعني "لا يساوي" في C ، get_stringويمكنها إما إرجاع قيمة سلسلة أو NULL، لذلك يمكننا المتابعة إذا s != NULLكان يجب أن تكون قيمة سلسلة إذا لم تكن كذلك NULL.

  • الآن برنامجنا ، إذا أردنا فقط طباعة كل حرف على سطر ، سيكون صحيحًا. لكن كيف يمكننا تحسين التصميم؟ حسنًا ، تذكر أن forالحلقة تهيئ القيمة أولاً ، وتتحقق من الشرط ، وبعد كل تكرار ، تزيد القيمة. لذلك في كل مسار في الحلقة ، نتحقق مما إذا كان i < strlen(s)لكنها strlen()دالة نسميها ، ونمررها sكوسيطة ، ونستخدم القيمة التي تعود للمقارنة بها iفي كل مرة نقوم بحساب طول السلسلة حتى لو لم يكن علينا ذلك. قد يبدو الحل الأفضل كما يلي:

    #include <cs50.h>
    #include <stdio.h>
    #include <string.h>

    int main(void)
    {
    string s = get_string();
    if (s != NULL)
    {
    for (int i = 0, n = strlen(s); i < n; i++)
    {
    printf("%c\n", s[i]);
    }
    }
    }
    • هذا مربي الحيوانات قليلاً ، لكننا نضع متغيرًا آخر n، على طول sفي البداية ، والآن نقارن رقمين في كل مرة ولا نضطر إلى إعادة حساب طول السلسلة.

  • بالنسبة إلى الأسلوب ، تكون أسماء المتغيرات الخاصة بنا قصيرة ، حيث لا يوجد لدينا سوى عدد قليل منها. يمكننا التعليق على كودنا:

    #include <cs50.h>
    #include <stdio.h>
    #include <string.h>

    int main(void)
    {
    // ask user for input
    string s = get_string();

    // make sure get_string returned a string
    if (s != NULL)
    {
    // iterate over the characters in s one at a time
    for (int i = 0, n = strlen(s); i < n; i++)
    {
    // print i'th character in s
    printf("%c\n", s[i]);
    }
    }
    }
    • نشرح الأسطر الأكثر إثارة للاهتمام في التعليمات البرمجية الخاصة بنا بالكلمات. و //في بداية علامات خط بأنها التعليق، مما يعني أن المترجم تجاهله. لكن الكود الآن مفهوم للبشر.

  • في C ، هناك ميزة أخرى تسمى typecastingتتيح لك تحويل نوع واحد من البيانات إلى نوع آخر. يتم تخزين الأحرف في الذاكرة كأرقام ثنائية ، حتى نتمكن من تحويلها ذهابًا وإيابًا.

  • تذكر أن ASCII هو معيار لتعيين الأحرف إلى الأحرف. فيما يلي بعض العينات:

    A B C D E F G H I ...
    65 66 67 68 69 70 71 72 73 ...

    a b c d e f g h i ...
    97 98 99 100 101 102 103 104 105 ...
  • يمكننا تجربة هذا البرنامج:

    #include <stdio.h>

    int main(void)
    {
    for (int i = 65; i < 65 + 26; i++)
    {
    printf("%c is %i\n", (char) i, i);
    }
    }
    • نحن نطبع iكشخصية عن طريق تلبيسها ، وذلك باستخدام (char) iلإخبار برنامجنا بالتعامل iكشخصية.

  • الآن إذا قمنا بتجميع وتشغيل برنامجنا ، فإننا نحصل على:

    A is 65
    B is 66
    C is 67
    ...
    Z is 90
  • ولكن يمكننا في الواقع أن نقول فقط printf("%c is %i\n", i, i);، iوسيتم طباعتها كحرف أيضًا ، لأن printfيعرف %cيعني أنه iيجب تنسيقه كحرف.

  • لكن انتظر ، إذا تمكنا من التعامل مع الأرقام مثل الأحرف ، فيمكننا أيضًا التعامل مع الأحرف مثل الأرقام:

    #include <stdio.h>

    int main(void)
    {
    for (char c = 'A'; c <= 'Z'; c++)
    {
    printf("%c is %i\n", c, c);
    }
    }
    • نحن نستخدم الآن cكما نستخدم عددًا صحيحًا i، وهذا يتكرر خلال جميع الأحرف الكبيرة كما في السابق. ونظرًا لأن الأحرف لها قيمة عددية بسبب ASCII ، يمكننا مقارنتها ببعضها البعض.

  • هناك نمط آخر يحتوي على أحرف ASCII: الحرف الصغير له قيمة 32 أعلى بالضبط من نفس الحرف في الأحرف الكبيرة.

  • ربما يمكننا تطبيق هذا:

    #include <cs50.h>
    #include <stdio.h>
    #include <string.h>

    int main(void)
    {
    string s = get_string();
    if (s != NULL)
    {
    for (int i = 0, n = strlen(s); i < n; i++)
    {
    if (s[i] >= 'a' && s[i] <= 'z')
    {
    printf("%c", s[i] - ('a' - 'A'));
    }
    else
    {
    printf("%c", s[i]);
    }
    }
    printf("\n");
    }
    }
    • الأسطر القليلة الأولى التي نعرفها بالفعل ، حيث نحصل على سلسلة من المستخدم ، ونقوم بالتكرار على كل حرف في السلسلة.

    • داخل الحلقة، على كل حرف، إذا كان الحرف هو بين aو zشاملة، وهو ما يعني إذا انها صغيرة، ونحن طباعة s[i] - ('a' - 'A')، وهو شخصية ناقص الفرق بين بريد إلكتروني صغيرة وحرف كبير. مما يجعلها كبيرة! (كان من الممكن أن نستخدمها للتو 32، ولكن من المفهوم أكثر أن نوضح من أين حصلنا على هذه القيمة.)

    • خلاف ذلك ، إذا لم تكن قيمة صغيرة ، فسنطبعها فقط.

  • لكن يمكننا حتى استخدام وظيفة تأتي مع C ، toupperفي المكتبة ctype.h(وسنكتشفها من خلال البحث في الكتب المرجعية أو عبر الإنترنت):

    #include <cs50.h>
    #include <ctype.h>
    #include <stdio.h>
    #include <string.h>

    int main(void)
    {
    string s = get_string();
    if (s != NULL)
    {
    for (int i = 0, n = strlen(s); i < n; i++)
    {
    if (islower(s[i]))
    {
    printf("%c", toupper(s[i]));
    }
    else
    {
    printf("%c", s[i]);
    }
    }
    printf("\n");
    }
    }
  • وفي الواقع ، toupperيتم تغيير الأحرف الصغيرة فقط إلى الأحرف الكبيرة ، لذلك يمكننا فعلاً:

    #include <cs50.h>
    #include <ctype.h>
    #include <stdio.h>
    #include <string.h>

    int main(void)
    {
    string s = get_string();
    if (s != NULL)
    {
    for (int i = 0, n = strlen(s); i < n; i++)
    {
    printf("%c", toupper(s[i]));
    }
    printf("\n");
    }
    }
    • يمكننا استخدام الأمر الموجود man toupperفي الجهاز الخاص بنا لقراءة الوظائف أو الأوامر ، مثل man strlenأو man printfيمكننا استخدام مفاتيح الأسهم الخاصة بنا للتمرير لأعلى ولأسفل (إنها مدرسة قديمة جدًا) ، qوللإنهاء.

    • وحتى لو toupperلم تكن مصحوبة بـ C ، لكان من الأفضل لنا أن ننفذها كوظيفة منفصلة ، لأن mainوظيفتنا أسهل كثيرًا في الفهم الآن.

  • دعنا نذهب إلى أبعد من ذلك في استكشاف السلاسل من خلال محاولة تنفيذ وظيفة مختلفة strlen، نحن أنفسنا:

    #include <cs50.h>
    #include <stdio.h>

    int main(void)
    {
    string s = get_string();
    int n = 0;
    while (s[n] != '\0')
    {
    n++;
    }
    printf("%i\n", n);
    }
    • سنحصل على سلسلة كالمعتاد ، وننشئ متغيرًا nلتخزين بعض الأرقام. سنبدأ عند 0، وبينما s[n]، الشخصية في هذا الفهرس s، ليست شيئًا يسمى \0، سنزيد n.

    • لكن لماذا يعمل هذا؟ تبين أنه يتم تخزين السلاسل بحرف في نهايته يشير إلى نهاية السلسلة ، نظرًا لعدم وجود طول محدد مسبقًا ، لذلك تبدو السلسلة في الذاكرة مثل:

      ------------------------------
      | Z | a | m | y | l | a | \0 |
      ------------------------------
  • يمكننا تمثيل المزيد من ذاكرة الكمبيوتر لدينا كشبكة:

    -----------------------------------
    | Z | a | m | y | l | a | \0 | A |
    -----------------------------------
    | n | d | i | \0 | | | | |
    -----------------------------------
    | | | | | | | | |
    -----------------------------------
    | | | | | | | | |
    -----------------------------------
    • يمكننا تخيل كل بايت (كل مربع في هذه الشبكة) من الذاكرة كما هو موضح من 0إلى 31، حيث يوجد إجمالي 32 بايت. وتتبع من حيث تبدأ سلاسل، يمكننا ببساطة نتذكر بداية سلسلة لدينا في الذاكرة، في حالة Zamyla، 0و Andi، 7في الواقع ، السلسلة في C هي مجرد موقع الحرف الأول في الذاكرة.

    • ومع \0C تشير إلى نهاية السلسلة.

  • يتم تخزين الأعداد الصحيحة وأنواع البيانات الأخرى أيضًا بنفس الطريقة في الذاكرة ، حتى لو كانت تستهلك المزيد من البايت.

  • بمجرد أن نفهم أن البيانات هي مجرد بايت في الذاكرة ، يمكننا التعامل معها بالكامل ويمكننا فعل كل شيء عن طريق كتابة برنامج.

  • و https://reference.cs50.net لديها الكثير من المعلومات المفيدة جدا، حول الوظائف التي تأتي مع C.

وسائط سطر الأوامر

  • دعنا نستخدم ما تعلمناه للتعمق أكثر في حجج سطر الأوامر.

  • حتى الآن ، اعتدنا int main(void)أن نبدأ برامجنا. تشير voidالكلمة الرئيسية على وجه الخصوص إلى أن برنامجنا لا يأخذ أي حجج.

  • ولكن ماذا لو أردنا كتابة برامج تأخذ مدخلات من سطر الأوامر ، أو كلمات بعد اسم البرنامج عند تشغيله في المحطة؟ على سبيل المثال ، قد نقوم بتشغيل make helloأو make cough0، والكلمة الثانية هناك حجة لبرنامجنا hello.

  • اتضح أنه يمكننا بدء برنامجنا بهذا: int main(int argc, string argv[])وسيتلقى حجج سطر الأوامر تلك.

  • الآن برنامجنا سيتلقى حجتين. الأول هو عدد صحيح مسمى argc، كما في عدد الوسيطة ، يخبرنا بعدد الحجج التي حصلنا عليها. والثاني عبارة عن مصفوفة ، أو قائمة ، من السلاسل ، تسمى argv، كما في متجه الوسيطة. يمكن الوصول إلى قائمة السلاسل هذه بنفس بناء الجملة كما نفعل مع الأحرف في سلسلة (نظرًا لأن السلسلة هي مجرد مجموعة من الأحرف) ، مثل argv[0].

  • دعنا نرى هذا في العمل:

    #include <cs50.h>
    #include <stdio.h>

    int main(int argc, string argv[])
    {
    if (argc == 2)
    {
    printf("hello, %s\n", argv[1]);
    }
    else
    {
    printf("hello, world\n");
    }
    }
    • هذا البرنامج ، عند تشغيله ، سيعيد شيئًا مثل ما يلي إذا أعطيناه وسيطة سطر أوامر:

      ~/workspace/ $ ./argv0 hello
      hello, hello
    • استخدمنا argv[1]لأنه argv[0]دائمًا اسم البرنامج نفسه.

    • عندما نجري فقط ./argv0، argcسوف يتم تمريرها إلى برنامجنا 1، لذلك سيقول فقط hello, world.

  • لنفعل هذا ، لنرى كيف يمكننا التكرار على مصفوفة:

    #include <cs50.h>
    #include <stdio.h>

    int main(int argc, string argv[])
    {
    for (int i = 0; i < argc; i++)
    {
    printf("%s\n", argv[i]);
    }
    }
    • يقوم هذا البرنامج بطباعة كل وسيطة ، أو كل سلسلة في argv، أثناء مرورها عبر الفهارس من 0إلى argc، والتي تخبرنا بعدد السلاسل الموجودة argv.

  • يمكننا أن نكون أكثر برودة. نظرًا لأننا نعلم argvأنها مصفوفة من السلاسل وكل سلسلة عبارة عن مصفوفة من الأحرف ، يمكننا الوصول مباشرة إلى الأحرف من argv:

    #include <cs50.h>
    #include <stdio.h>
    #include <string.h>

    int main(int argc, string argv[])
    {
    // iterate over strings in argv
    for (int i = 0; i < argc; i++)
    {
    // iterate over characters in current string
    for (int j = 0, n = strlen(argv[i]); j < n; j++)
    {
    // print j'th character in i'th string
    printf("%c\n", argv[i][j]);
    }
    printf("\n");
    }
    }
    • forالحلقة الخارجية ، مع i، تتكرر فوق كل وتر في argv.

    • forالحلقة الداخلية ، مع j، تنظر إلى argv[i]، ولكل حرف بداخلها ، تطبعها في سطر جديد.

    • ثم تكرر الحلقة الداخلية للسلسلة التالية.

    • مع argv[i][j]يمكننا الحصول على شخصية فردية في argv.

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

  • بالطبع ، نريد أن نرى هذا مباشرة:

    #include <cs50.h>
    #include <stdio.h>

    int main(int argc, string argv[])
    {
    if (argc != 2)
    {
    printf("missing command-line argument\n");
    return 1;
    }
    printf("hello, %s\n", argv[1]);
    return 0;
    }
    • الآن ، إذا لم يحصل البرنامج على وسيط سطر أوامر ، فسيتم إنهاء البرنامج بالعودة ، وبقيمة 1.

    • خلافًا لذلك ، سنطبع المعامل ونعيد القيمة بشكل صريح 0عند الخروج.

  • يمكننا رؤية كود الخروج في المحطة مثل هذا:

    ~/workspace/ $ ./exit
    missing command-line argument
    ~/workspace/ $ echo $?
    1
    • $?هو رمز سحري لكود الخروج من البرنامج السابق ، echoوهو عبارة عن برنامج سطر أوامر يقوم فقط بطباعة القيم.

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

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

  • يا له من يوم مثير! المزيد من المرح يأتي الأسبوع المقبل!

تعليقات