قراءة موجّهة · الملاحظات

الأسبوع 2: المصفوفات

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

جلسة دراسة مقترحة: حتى 55 دقيقةWeek 2
محاور هذه الخطوة
  • المصفوفات
  • السلاسل النصية
  • سطر الأوامر
  • النطاق

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

الأسبوع 2: المصفوفات

المصدر الأصلي على CS50

المحاضرة الثانية (Lecture 2)

مرحبًا! (Welcome!)

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

مستويات القراءة (Reading Levels)

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

تصحيح الأخطاء (Debugging)

  • سوف يرتكب الجميع أخطاء أثناء البرمجة.
  • تصحيح الأخطاء هي عملية تحديد موقع الأخطاء وإزالتها من الشيفرة الخاصة بك.
  • يُطلق على إحدى تقنيات تصحيح الأخطاء التي ستستخدمها أثناء هذه الدورة التدريبية لتصحيح أخطاء الشيفرة الخاصة بك تصحيح أخطاء البطة المطاطية، حيث يمكنك التحدث إلى كائن غير حي (أو إلى نفسك) للمساعدة في التفكير في الشيفرة الخاصة بك ولماذا لا تعمل على النحو المنشود. عندما تواجه تحديات مع الشيفرة الخاص بك، فكر في كيفية التحدث بصوت عالٍ إلى بطة مطاطية حول مشكلة الشيفرة. إذا كنت تفضل عدم التحدث إلى بطة بلاستيكية صغيرة، فنحن نرحب بك للتحدث إلى إنسان قريب منك!
  • لقد أنشأنا CS50 Duck و CS50.ai كأدوات يمكنها مساعدتك في تصحيح أخطاء الشيفرة الخاصة بك.
  • خذ بعين الاعتبار الشيفرة التالي:

    // Missing #include for stdio.h
    
    int main(void)
    {
        printf("hello, world\n");
    }
    

    لاحظ كيف يتم استدعاء الدالة #include توجيه لـ stdio.h مفقود. هذا الملف الرأسي مطلوب لـ وظيفة printf لتعمل بشكل صحيح. وبدون ذلك، لن يتعرف المترجم على وظيفة printf وستؤدي إلى ظهور خطأ.

  • بالمثل، ضع في اعتبارك الشيفرة التالي:

    // Misspelled stdio.h
    
    #include <studio.h>
    
    int main(void)
    {
        printf("hello, world\n");
    }
    

    لاحظ كيف stdio.h به خطأ إملائي كـ studio.h. سيؤدي هذا الخطأ المطبعي إلى حدوث خطأ في الترجمة لأن المترجم لا يمكنه العثور على ملف يسمى studio.h. اسم ملف الرأس الصحيح هو stdio.h، والتي تعني "الإدخال/الإخراج القياسي".

  • قد ننسى أن نعلن عن نوع المتغير:

    // Missing cs50.h, variable's type, semicolon, %s, and second printf argument.
    
    #include <stdio.h>
    
    int main(void)
    {
        name = get_string("What's your name? ")
        printf("hello, world\n");
    }
    

    لاحظ وجود أخطاء متعددة. أولاً: نوع لم يتم التصريح عن name . ثانيا، مكتبة cs50.h مفقودة للسماح لنا باستخدامها string. ثالثًا، هناك فاصلة منقوطة مفقودة بعد get_string مكالمة. رابعا، لا تستخدم عبارة printf فعليًا name متغير.

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

    mario

  • خذ بعين الاعتبار الشيفرة التالي الذي يحتوي على خطأ تم إدراجه عمدًا بداخله:

    // Buggy example for printf
    
    #include <stdio.h>
    
    int main(void)
    {
        for (int i = 0; i <= 3; i++)
        {
            printf("#\n");
        }
    }
    

    لاحظ أن هذا الشيفرة يطبع أربع كتل بدلاً من ثلاث.

  • النوع code buggy.c في النافذة الطرفية واكتب الشيفرة أعلاه.
  • عند تشغيل هذا الرمز، تظهر أربعة قوالب بدلاً من الثلاثة المقصودة.
  • printf هي طريقة مفيدة جدًا لتصحيح أخطاء الشيفرة الخاصة بك. يمكنك تعديل الشيفرة الخاص بك على النحو التالي:

    // Buggy example for printf
    
    #include <stdio.h>
    
    int main(void)
    {
        for (int i = 0; i <= 3; i++)
        {
            printf("i is %i\n", i);
            printf("#\n");
        }
    }
    

    لاحظ كيف يقوم هذا الشيفرة بإخراج قيمة i أثناء كل تكرار للحلقة حتى نتمكن من تصحيح الشيفرة الخاصة بنا.

  • بتشغيل هذا الشيفرة، سوف ترى العديد من العبارات، بما في ذلك i is 0, i is 1, i is 2و i is 3. عند رؤية هذا، قد تدرك أن الشيفرة الإضافية تحتاج إلى تصحيح على النحو التالي: تم استبدال

    #include <stdio.h>
    
    int main(void)
    {
        for (int i = 0; i < 3; i++)
        {
            printf("#\n");
        }
    }
    

    لاحظ <= بـ <.

  • يمكن تحسين هذا الشيفرة كما يلي:

    // Buggy example for debug50
    
    #include <cs50.h>
    #include <stdio.h>
    
    void print_column(int height);
    
    int main(void)
    {
        int h = get_int("Height: ");
        print_column(h);
    }
    
    void print_column(int height)
    {
        for (int i = 0; i <= height; i++)
        {
            printf("#\n");
        }
    }
    

    لاحظ أن تجميع هذا الرمز وتشغيله لا يزال يؤدي إلى حدوث خطأ.

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

    breakpoint

  • ثانيًا، اركض debug50 ./buggy. ستلاحظ أنه بعد تشغيل مصحح الأخطاء وسيضيء سطر من الشيفرة الخاصة بك بلون يشبه الذهب. بكل معنى الكلمة، الشيفرة موجود متوقف مؤقتًا في هذا السطر من الشيفرة. لاحظ في الزاوية العلوية اليسرى كيف يتم عرض جميع المتغيرات المحلية، بما في ذلك h، والتي ليس لها قيمة حاليًا. في الجزء العلوي من نافذتك، يمكنك النقر فوق زر step over ، وسيستمر في التحرك عبر الشيفرة الخاصة بك. لاحظ كيف قيمة i يزداد كلما تقدمت خلال الحلقة.
  • على الرغم من أن هذه الأداة لن تظهر لك مكان الخطأ الخاص بك، إلا أنها ستساعدك على إبطاء السرعة ومعرفة كيفية تشغيل الشيفرة الخاصة بك خطوة بخطوة. يمكنك استخدام step into كطريقة لمزيد من البحث في تفاصيل رمز الأخطاء الخاص بك.
  • الطريقة الثالثة لتصحيح الأخطاء هي التحدث إلى بطة مطاطية أو كائن غير حي أو شخص لوصف المشكلة التي تواجهها والخطوات المحددة التي تتخذها لحل هذه المشكلة كوسيلة لاكتشاف خطأك.
  • أخيرًا، ، المعروف أيضًا باسم *CS50 Duck*، يمكن أن يساعدك في تصحيح أخطاء الشيفرة الخاصة بك.

التجميع (Compiling)

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

    flowchart LR
        in["الشيفرة المصدرية"] --> BOX[" compiler "]
        BOX --> out["شيفرة الآلة"]
    
  • على سبيل المثال، قد يكون لديك برنامج كمبيوتر يبدو كالتالي:

    #include <stdio.h>
    
    int main(void)
    {
        printf("hello, world\n");
    }
    
  • سيأخذ المترجم الشيفرة أعلاه ويحوله إلى كود الجهاز الذي قد يبدو كالتالي:

    01010100 01001000 01001001 01010011
    00100000 01001001 01010011 00100000
    01000011 01010011 00110101 00110000
    

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

  • VS Code، بيئة البرمجة المقدمة لك كطالب CS50، تستخدم مترجمًا يسمى clang (والتي تعني "الواجهة الأمامية لعائلة لغة C").
  • يمكنك إدخال ما يلي في النافذة الطرفية لتجميع الشيفرة الخاصة بك: clang -o hello hello.c.
  • وسيطات سطر الأوامر يتم توفير في سطر الأوامر ل clang ك -o hello hello.c.
  • قيد التشغيل ./hello في نافذة الوحدة الطرفية، يعمل برنامجك على النحو المنشود.
  • خذ بعين الاعتبار الشيفرة التالي من الأسبوع الماضي:

    #include <cs50.h>
    #include <stdio.h>
    
    int main(void)
    {
        string name = get_string("What's your name? ");
        printf("hello, %s\n", name);
    }
    
  • لتجميع هذا الرمز، يمكنك الكتابة clang -o hello hello.c -lcs50.
  • إذا أردت الكتابة make hello، يقوم بتشغيل أمر ينفذ clang to create an output file that you can run as a user.
  • تمت برمجة VS Code مسبقًا على هذا النحو سيقوم make بتشغيل العديد من وسائط سطر الأوامر مع clang for your convenience as a user.
  • بينما يتم تقديم ما ورد أعلاه كتوضيح، بحيث يمكنك فهم عملية ومفهوم تجميع الشيفرة بشكل أعمق، باستخدام make في CS50 جيد تمامًا وهو التوقع!
  • يتضمن التجميع أربع خطوات رئيسية، بما في ذلك ما يلي:
  • أولاً، المعالجة المسبقة هو المكان الذي توجد فيه ملفات الرأس في الشيفرة الخاصة بك، والتي تم تحديدها بواسطة a # (such as #include <cs50.h>) يتم نسخها ولصقها بشكل فعال في ملفك. خلال هذه الخطوة، الشيفرة من تم نسخ cs50.h إلى برنامجك. وبالمثل، تماما كما يحتوي على الشيفرة الخاصة بك #include <stdio.h>، الشيفرة موجود بداخله يتم نسخ stdio.h في مكان ما على جهاز الكمبيوتر الخاص بك إلى برنامجك. ويمكن تصور هذه الخطوة على النحو التالي:

      string get_string(string prompt);
      int printf(string format, ...);
    
      int main(void)
      {
          string name = get_string("What's your name? ");
          printf("hello, %s\n", name);
      }
    
  • ثانيًا، في مرحلة الترجمة (compiling)، يتحول برنامجك إلى شيفرة التجميع (assembly code). ويمكن تصور هذه الخطوة كما يلي:

    ...
    main:
        .cfi_startproc
    # BB#0:
        pushq    %rbp
    .Ltmp0:
        .cfi_def_cfa_offset 16
    .Ltmp1:
        .cfi_offset %rbp, -16
        movq    %rsp, %rbp
    .Ltmp2:
        .cfi_def_cfa_register %rbp
        subq    $16, %rsp
        xorl    %eax, %eax
        movl    %eax, %edi
        movabsq    $.L.str, %rsi
        movb    $0, %al
        callq    get_string
        movabsq    $.L.str.1, %rdi
        movq    %rax, -8(%rbp)
        movq    -8(%rbp), %rsi
        movb    $0, %al
        callq    printf
        ...
    
  • ثالثًا، في مرحلة التجميع (assembling)، يحوّل المُجمِّع (assembler)، وهو أداة ضمن سلسلة أدوات المترجم، شيفرة التجميع إلى شيفرة الآلة. ويمكن تصور هذه الخطوة كما يلي:

    01111111010001010100110001000110
    00000010000000010000000100000000
    00000000000000000000000000000000
    00000000000000000000000000000000
    00000001000000000011111000000000
    00000001000000000000000000000000
    00000000000000000000000000000000
    ...
    
  • أخيرًا، في مرحلة الربط (linking)، تُدمج شيفرة الآلة المترجمة مسبقًا من المكتبات التي ضمّنتها مع شيفرتك، ثم يُنتج الملف التنفيذي النهائي.

    01111111010001010100110001000110
    00000010000000010000000100000000
    00000000000000000000000000000000
    00000000000000000000000000000000
    00000001000000000011111000000000
    00000001000000000000000000000000
    00000000000000000000000000000000
    00000000000000000000000000000000
    00000000000000000000000000000000
    00000000000000000000000000000000
    10100000000000100000000000000000
    00000000000000000000000000000000
    00000000000000000000000000000000
    01000000000000000000000000000000
    00000000000000000100000000000000
    00001010000000000000000100000000
    01010101010010001000100111100101
    01001000100000111110110000010000
    00110001110000001000100111000111
    01001000101111100000000000000000
    00000000000000000000000000000000
    00000000000000001011000000000000
    11101000000000000000000000000000
    00000000010010001011111100000000
    00000000000000000000000000000000
    00000000000000000000000001001000
    ...
    

المصفوفات (Arrays)

  • في الأسبوع 0، تحدثنا عن ذلك أنواع البيانات مثل bool, int, char, string، إلخ.
  • يتطلب كل نوع بيانات مقدارًا معينًا من موارد النظام (هذه هي الأحجام النموذجية في بيئة CS50):
    • bool 1 بايت
    • int 4 بايت
    • long 8 بايت
    • float 4 بايت
    • double 8 بايت
    • char 1 بايت
    • string ؟ بايت
  • داخل جهاز الكمبيوتر الخاص بك، لديك كمية محدودة من الذاكرة المتوفرة.

    memory

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

    1 byte

  • وبالمثل، int، والذي يتطلب 4 بايت، قد يبدو كما يلي:

    4 bytes

  • يمكننا إنشاء برنامج يستكشف هذه المفاهيم. داخل المحطة الخاصة بك، اكتب code scores.c واكتب الشيفرة كما يلي:

    // Averages three (hardcoded) numbers
    
    #include <stdio.h>
    
    int main(void)
    {
        // Scores
        int score1 = 72;
        int score2 = 73;
        int score3 = 33;
    
        // Print average
        printf("Average: %f\n", (score1 + score2 + score3) / 3.0);
    }
    

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

  • قيد التشغيل make scores يجمع البرنامج. ثم الجري ./scores ينفذه.
  • يمكنك أن تتخيل كيفية تخزين هذه المتغيرات في الذاكرة:

    scores in memory

  • المصفوفات عبارة عن سلسلة من القيم المخزنة بالتعاقب في الذاكرة.
  • int scores[3] هي طريقة لإخبار المترجم بتزويدك بثلاثة أماكن متتالية في ذاكرة الحجم int لتخزين ثلاثة scores. بالنظر إلى برنامجنا، يمكنك مراجعة الشيفرة الخاصة بك على النحو التالي: يقوم

    // Averages three (hardcoded) numbers using an array
    
    #include <cs50.h>
    #include <stdio.h>
    
    int main(void)
    {
        // Scores
        int scores[3];
        scores[0] = 72;
        scores[1] = 73;
        scores[2] = 33;
    
        // Print average
        printf("Average: %f\n", (scores[0] + scores[1] + scores[2]) / 3.0);
    }
    

    لاحظ ذلك تم الآن إنشاء scores[0] بفحص القيمة في هذا الموقع من الذاكرة indexing into تم استدعاء المصفوفة scores في الموقع 0 لمعرفة القيمة المخزنة هناك.

  • يمكنك أن ترى كيف أنه بينما يعمل الشيفرة أعلاه، لا تزال هناك فرصة لتحسين الشيفرة الخاص بنا. قم بمراجعة الشيفرة الخاصة بك على النحو التالي:

    // Averages three numbers using an array and a loop
    
    #include <cs50.h>
    #include <stdio.h>
    
    int main(void)
    {
        // Get scores
        int scores[3];
        for (int i = 0; i < 3; i++)
        {
            scores[i] = get_int("Score: ");
        }
    
        // Print average
        printf("Average: %f\n", (scores[0] + scores[1] + scores[2]) / 3.0);
    }
    

    لاحظ كيف نقوم بالفهرسة scores باستخدام يتم توفير scores[i] حيث i بواسطة for حلقة.

  • يمكننا تبسيط أو مجردة بعيدا حساب المتوسط. قم بتعديل الشيفرة الخاص بك كما يلي: تم الإعلان عن

    // Averages three numbers using an array, a constant, and a helper function
    
    #include <cs50.h>
    #include <stdio.h>
    
    // Constant
    const int N = 3;
    
    // Prototype
    float average(int length, int array[]);
    
    int main(void)
    {
        // Get scores
        int scores[N];
        for (int i = 0; i < N; i++)
        {
            scores[i] = get_int("Score: ");
        }
    
        // Print average
        printf("Average: %f\n", average(N, scores));
    }
    
    float average(int length, int array[])
    {
        // Calculate average
        int sum = 0;
        for (int i = 0; i < length; i++)
        {
            sum += array[i];
        }
        return sum / (float) length;
    }
    

    لاحظ أنه تم استدعاء دالة جديدة average . علاوة على ذلك، لاحظ أ const أو القيمة الثابتة لـ تم الإعلان عن N . والأهم من ذلك، لاحظ كيف average تستغرق الوظيفة int array[]، مما يعني أن الوظيفة يمكن أن تتلقى مصفوفة كمعلمة.

  • لا يمكن للمصفوفات أن تكون حاويات فحسب، بل يمكن تمريرها بين الوظائف.

سلاسل (Strings)

  • A string هو ببساطة مصفوفة من القيم من النوع char: مجموعة من الأحرف.
  • للاستكشاف char و string، اكتب code hi.c في نافذة الوحدة الطرفية واكتب الشيفرة كما يلي:

    // Prints chars
    
    #include <stdio.h>
    
    int main(void)
    {
        char c1 = 'H';
        char c2 = 'I';
        char c3 = '!';
    
        printf("%c%c%c\n", c1, c2, c3);
    }
    

    لاحظ أن هذا سيؤدي إلى إخراج سلسلة من الأحرف.

  • وبالمثل، قم بإجراء التعديل التالي على الشيفرة الخاص بك:

    // Prints chars' ASCII codes
    
    #include <stdio.h>
    
    int main(void)
    {
        char c1 = 'H';
        char c2 = 'I';
        char c3 = '!';
    
        printf("%i %i %i\n", c1, c2, c3);
    }
    

    لاحظ أنه تتم طباعة رموز ASCII عن طريق الاستبدال %c مع %i.

  • بالنظر إلى الصورة التالية، يمكنك أن ترى كيف أن السلسلة عبارة عن مجموعة من الأحرف التي تبدأ بالحرف الأول وتنتهي بحرف خاص يسمى a NUL character (ملاحظة: NUL الذي يحتوي على حرف L واحد هو الحرف '\0'، ويختلف عن NULL الذي يحتوي على حرفين L):

    hi with terminator

  • بتخيل ذلك بالنظام العشري، ستبدو المصفوفة كما يلي:

    hi with decimal

  • ويمكننا أن نتصور ما سبق على النحو التالي:

    // Prints string
    
    #include <cs50.h>
    #include <stdio.h>
    
    int main(void)
    {
        string s = "HI!";
        printf("%s\n", s);
    }
    

    لاحظ أنه يتم تمثيل كافة الأحرف داخل ملف string.

  • لفهم كيفية عمل أ string ، قم بمراجعة الشيفرة الخاصة بك على النحو التالي: تعرض عبارة

    // Treats string as array
    
    #include <cs50.h>
    #include <stdio.h>
    
    int main(void)
    {
        string s = "HI!";
        printf("%c%c%c\n", s[0], s[1], s[2]);
    }
    

    لاحظ كيف يتم استدعاء الدالة printf ثلاث قيم من المصفوفة الخاصة بنا تسمى s.

  • كما كان من قبل، يمكننا الاستبدال %c مع %i على النحو التالي:

    // Prints string's ASCII codes, including NUL
    
    #include <cs50.h>
    #include <stdio.h>
    
    int main(void)
    {
        string s = "HI!";
        printf("%i %i %i %i\n", s[0], s[1], s[2], s[3]);
    }
    

    لاحظ أن هذا يؤدي إلى طباعة رموز ASCII للسلسلة، بما في ذلك NUL.

  • دعونا نتخيل أننا نريد أن نقول كليهما HI! و BYE!. قم بتعديل الشيفرة الخاص بك كما يلي:

    // Multiple strings
    
    #include <cs50.h>
    #include <stdio.h>
    
    int main(void)
    {
        string s = "HI!";
        string t = "BYE!";
    
        printf("%s\n", s);
        printf("%s\n", t);
    }
    

    لاحظ أنه تم الإعلان عن سلسلتين واستخدامهما في هذا المثال.

  • يمكنك تصور ذلك على النحو التالي:

    hi and bye

  • يمكننا تحسين هذا الرمز بشكل أكبر. قم بتعديل الشيفرة الخاص بك كما يلي:

    // Array of strings
    
    #include <cs50.h>
    #include <stdio.h>
    
    int main(void)
    {
        string words[2];
    
        words[0] = "HI!";
        words[1] = "BYE!";
    
        printf("%s\n", words[0]);
        printf("%s\n", words[1]);
    }
    

    لاحظ أنه يتم تخزين كلا السلسلتين ضمن مصفوفة واحدة من النوع string.

  • يمكننا دمج السلسلتين في مصفوفة من السلاسل.

    #include <cs50.h>
    #include <stdio.h>
    
    int main(void)
    {
        string words[2];
    
        words[0] = "HI!";
        words[1] = "BYE!";
    
        printf("%c%c%c\n", words[0][0], words[0][1], words[0][2]);
        printf("%c%c%c%c\n", words[1][0], words[1][1], words[1][2], words[1][3]);
    }
    

    لاحظ أن مجموعة من تم إنشاء words . إنها مجموعة من السلاسل. يتم تخزين كل كلمة في words.

طول السلسلة (String Length)

  • من المشاكل الشائعة في البرمجة، وربما لغة C بشكل أكثر تحديدًا، اكتشاف طول السلسلة. كيف يمكننا تنفيذ ذلك في الشيفرة؟ اكتب code length.c في النافذة الطرفية والرمز كما يلي:

    // Determines the length of a string
    
    #include <cs50.h>
    #include <stdio.h>
    
    int main(void)
    {
        // Prompt for user's name
        string name = get_string("Name: ");
    
        // Count number of characters up until '\0' (aka NUL)
        int n = 0;
        while (name[n] != '\0')
        {
            n++;
        }
        printf("%i\n", n);
    }
    

    لاحظ أن هذا الرمز يتكرر حتى يتم العثور على الحرف NUL.

  • يمكن تحسين هذا الشيفرة عن طريق تجريد العد في دالة كما يلي:

    // Determines the length of a string using a function
    
    #include <cs50.h>
    #include <stdio.h>
    
    int string_length(string s);
    
    int main(void)
    {
        // Prompt for user's name
        string name = get_string("Name: ");
        int length = string_length(name);
        printf("%i\n", length);
    }
    
    int string_length(string s)
    {
        // Count number of characters up until '\0' (aka NUL)
        int n = 0;
        while (s[n] != '\0')
        {
            n++;
        }
        return n;
    }
    

    لاحظ أنه تم استدعاء دالة جديدة string_length بحساب الأحرف حتى يتم تحديد موقع NUL.

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

    // Determines the length of a string using a function
    
    #include <cs50.h>
    #include <stdio.h>
    #include <string.h>
    
    int main(void)
    {
        // Prompt for user's name
        string name = get_string("Name: ");
        int length = strlen(name);
        printf("%i\n", length);
    }
    

    لاحظ أن هذا الشيفرة يستخدم الامتداد مكتبة string.h ، المعلن عنها في أعلى الملف. علاوة على ذلك، فإنه يستخدم وظيفة من تلك المكتبة تسمى strlen، الذي يحسب طول السلسلة التي تم تمريرها إليها.

  • يمكن أن يقف الشيفرة الخاص بنا على أكتاف المبرمجين الذين سبقوهم ويستخدمون المكتبات التي أنشأوها.
  • ctype.h هي مكتبة أخرى مفيدة جدًا. تخيل أننا أردنا إنشاء برنامج يقوم بتحويل كافة الأحرف الصغيرة إلى أحرف كبيرة. في النافذة الطرفية، اكتب code uppercase.c واكتب الشيفرة كما يلي:

    // Uppercases a string
    
    #include <cs50.h>
    #include <stdio.h>
    #include <string.h>
    
    int main(void)
    {
        string s = get_string("Before: ");
        printf("After:  ");
        for (int i = 0, n = strlen(s); i < n; i++)
        {
            if (s[i] >= 'a' && s[i] <= 'z')
            {
                printf("%c", s[i] - 32);
            }
            else
            {
                printf("%c", s[i]);
            }
        }
        printf("\n");
    }
    

    لاحظ أن هذا الرمز يتكرر من خلال كل قيمة في السلسلة. البرنامج ينظر إلى كل حرف. إذا كان الحرف صغيرًا، فإنه يطرح 32 من قيمة ASCII للحرف لتحويله إلى أحرف كبيرة.

  • بالتذكير بعملنا السابق في الأسبوع الماضي، ربما تتذكر مخطط قيم ASCII هذا:

    0 NUL 16 DLE 32 SP 48 0 64 @ 80 P 96 ` 112 p
    1 SOH 17 DC1 33 ! 49 1 65 A 81 Q 97 a 113 q
    2 STX 18 DC2 34 50 2 66 B 82 R 98 b 114 r
    3 ETX 19 DC3 35 # 51 3 67 C 83 S 99 c 115 s
    4 EOT 20 DC4 36 $ 52 4 68 D 84 T 100 d 116 t
    5 ENQ 21 NAK 37 % 53 5 69 E 85 U 101 e 117 u
    6 ACK 22 SYN 38 & 54 6 70 F 86 V 102 f 118 v
    7 BEL 23 ETB 39 55 7 71 G 87 W 103 g 119 w
    8 BS 24 CAN 40 ( 56 8 72 H 88 X 104 h 120 x
    9 HT 25 EM 41 ) 57 9 73 I 89 Y 105 i 121 y
    10 LF 26 SUB 42 * 58 : 74 J 90 Z 106 j 122 z
    11 VT 27 ESC 43 + 59 ; 75 K 91 [ 107 k 123 {
    12 FF 28 FS 44 , 60 < 76 L 92 \ 108 l 124 |
    13 CR 29 GS 45 - 61 = 77 M 93 ] 109 m 125 }
    14 SO 30 RS 46 . 62 > 78 N 94 ^ 110 n 126 ~
    15 SI 31 US 47 / 63 ? 79 O 95 _ 111 o 127 DEL
  • عند وجود حرف ASCII صغير (a-z). 32 بطرحه منه، ينتج عنه النسخة الكبيرة من نفس الحرف. لاحظ أن هذا يعمل فقط مع أحرف ASCII من a إلى z، وليس مع الأحرف المحركة أو غير ASCII.
  • بينما يقوم البرنامج بما نريده، هناك طريقة أسهل باستخدام مكتبة ctype.h . قم بتعديل برنامجك كما يلي:

    // Uppercases string using ctype library (and an unnecessary condition)
    
    #include <cs50.h>
    #include <ctype.h>
    #include <stdio.h>
    #include <string.h>
    
    int main(void)
    {
        string s = get_string("Before: ");
        printf("After:  ");
        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 s[i]. يتم تحويل كل حرف (إذا كانت صغيرة) إلى أحرف كبيرة.

  • من الجدير بالذكر ذلك toupper يعرف تلقائيًا الأحرف الكبيرة والأحرف الصغيرة فقط. وبالتالي، يمكن تبسيط الشيفرة الخاصة بك على النحو التالي:

    // Uppercases string using ctype library
    
    #include <cs50.h>
    #include <ctype.h>
    #include <stdio.h>
    #include <string.h>
    
    int main(void)
    {
        string s = get_string("Before: ");
        printf("After:  ");
        for (int i = 0, n = strlen(s); i < n; i++)
        {
            printf("%c", toupper(s[i]));
        }
        printf("\n");
    }
    

    لاحظ أن هذا الرمز يُكتب بأحرف كبيرة على سلسلة باستخدام مكتبة ctype .

  • يمكنك أن تقرأ عن كافة إمكانيات مكتبة ctype على صفحات الدليل.

وسيطات سطر الأوامر (Command-Line Arguments)

  • Command-line arguments هي تلك الوسائط التي يتم تمريرها إلى برنامجك في سطر الأوامر. على سبيل المثال، كل تلك البيانات التي كتبتها بعد clang تعتبر وسيطات سطر الأوامر. يمكنك استخدام هذه الحجج في برامجك الخاصة!
  • في نافذتك الطرفية، اكتب code greet.c واكتب الشيفرة كما يلي:

    // Uses get_string
    
    #include <cs50.h>
    #include <stdio.h>
    
    int main(void)
    {
        string answer = get_string("What's your name? ");
        printf("hello, %s\n", answer);
    }
    

    لاحظ أن هذا يقول hello للمستخدم.

  • مع ذلك، ألن يكون من الجيد أن تكون قادرًا على قبول الحجج حتى قبل تشغيل البرنامج؟ قم بتعديل الشيفرة الخاص بك كما يلي:

    // Prints a command-line argument
    
    #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");
        }
    }
    

    لاحظ أن هذا البرنامج يعرف كليهما argcعدد وسائط سطر الأوامر و argv، وهو عبارة عن مصفوفة من السلاسل التي تم تمريرها كوسائط في سطر الأوامر.

  • لذلك، باستخدام بناء جملة هذا البرنامج، يتم تنفيذه ./greet David سيؤدي إلى قول البرنامج hello, David.
  • يمكنك طباعة كل من وسيطات سطر الأوامر بما يلي:

    // Prints command-line arguments
    
    #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]) هو دائمًا اسم البرنامج نفسه، متبوعًا بأي وسيطات تقدمها عند تشغيل البرنامج.

الخروج من الحالة (Exit Status)

  • عندما ينتهي البرنامج، يتم توفير رمز خروج خاص للكمبيوتر.
  • عند خروج البرنامج بدون خطأ، يظهر رمز الحالة يتم توفير 0 للكمبيوتر. في كثير من الأحيان، عند حدوث خطأ يؤدي إلى إنهاء البرنامج، تظهر حالة يتم توفير 1 للكمبيوتر.
  • يمكنك كتابة برنامج كالتالي يوضح ذلك عن طريق الكتابة code status.c وكتابة الشيفرة كالتالي:

    // Returns explicit value from main
    
    #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;
    }
    

    لاحظ أنه إذا فشلت في تقديم ./status David، سوف تحصل على حالة الخروج 1. ومع ذلك، إذا قمت بتوفير ./status David، سوف تحصل على حالة الخروج 0.

  • يمكنك الكتابة echo $? في المحطة لمعرفة حالة الخروج لأمر التشغيل الأخير.
  • يمكنك أن تتخيل كيف يمكنك استخدام أجزاء من البرنامج أعلاه للتحقق مما إذا كان المستخدم قد قدم العدد الصحيح من وسيطات سطر الأوامر.

التلخيص (Summing Up)

في هذا الدرس، تعلمت المزيد من التفاصيل حول تجميع البيانات وكيفية تخزينها داخل جهاز الكمبيوتر. وعلى وجه التحديد، تعلمت…

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

نراكم في المرة القادمة!

أنهيت قراءة الملاحظات؟

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