الاسبوع التانى 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_charget_doubleget_floatget_intget_long_longget_string...
وأنواع البيانات مثل:
boolchardoublefloatintlong longstring...
واجهنا أيضًا بعض حدود الحوسبة ، مثل تجاوز عدد صحيح أو عدم دقة تعويم ، والتي يمكن أن يكون لها تأثير شديد في العالم الحقيقي.
تصحيح
بشكل عام ، تأتي الأخطاء من المترجم ،
makeأوclangكما سنوضح:int main(void){printf("hello, world\n");}عندما نحاول تجميع هذا فقط ، نحصل على خطأ يشبه:
~/workspace/ $ make buggy0clang -ggdb3 -O0 -std=c11 -Wall -Werror -Wshadow buggy0.c -lcs50 -lm -o buggy0buggy0.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/ $ ./buggy3n is 1n is 2n is 3n is 50n is -50-50 is a negative integer~/workspace/ $ ./buggy3n is -1-1 is a negative integer~/workspace/ $ ./buggy3n is -2-2 is a negative integer~/workspace/ $ ./buggy3n is 00 is a negative integerحسنًا ، يبدو أنه يعمل باستثناء تلك الحالة الأخيرة. حسنًا ، يمكننا إلقاء نظرة على الكود الخاص بنا لمحاولة اكتشاف مكان حدوث هذا الخطأ ، ولكن بمجرد أن يصبح برنامجنا أكثر تعقيدًا ، سنحتاج إلى بعض الأدوات الأفضل أيضًا.
يمكننا استخدام أداة CS50 أخرى تسمى
debug50، والتي تتيح لنا تشغيل برنامجنا خطوة بخطوة ، وسطرًا بسطر ، وإلقاء نظرة على ما يحدث.
هيا نركض:
~/workspace/ $ debug50 ./buggy3n isظهرت لوحة على اليمين ، مع حدوث الكثير:
ولكن إذا كتبنا رقمًا لبرنامجنا ، فيبدو أنه يفعل الشيء الطبيعي ويخبرنا أنه خارج:
~/workspace/ $ debug50 ./buggy3n is -1-1 is a negative integerChild exited with status 0GDBserver 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 to0، بمعنى أننا نحصلs[0]على الحرف الأول في السلسلة ، ثمs[1]، ثمs[2]، وهكذا ، حتى تتم طباعة كل حرف في السلسلة:Zamyla
ولكن ماذا يحدث إذا كتب المستخدم ، على سبيل المثال ، سلسلة طويلة حقًا أو فعل شيئًا يتسبب في حدوث خطأ؟ حسنًا ،
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]);}}}A
!=تعني "لا يساوي" في 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 inputstring s = get_string();// make sure get_string returned a stringif (s != NULL){// iterate over the characters in s one at a timefor (int i = 0, n = strlen(s); i < n; i++){// print i'th character in sprintf("%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 65B is 66C 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 hellohello, 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 argvfor (int i = 0; i < argc; i++){// iterate over characters in current stringfor (int j = 0, n = strlen(argv[i]); j < n; j++){// print j'th character in i'th stringprintf("%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/ $ ./exitmissing command-line argument~/workspace/ $ echo $?1$?هو رمز سحري لكود الخروج من البرنامج السابق ،echoوهو عبارة عن برنامج سطر أوامر يقوم فقط بطباعة القيم.قد لا نبحث عن هذا كثيرًا ، لكن المصححات والبرامج الأخرى قد تبحث عنه لتحديد ما إذا كانت هناك أية أخطاء.
لا تنس أن المصفوفة هي جزء كبير من الذاكرة المستمرة ، مع كل عنصر بداخلها متجاور ، متتاليًا متتاليًا. وهذه العناصر بشكل عام هي نفس نوع البيانات ، حيث لدينا عادة مصفوفات من الأحرف أو الأعداد الصحيحة. سنحتاج هذه البنية لحل المشكلات الأكثر تعقيدًا ، مثل الفرز والبحث.
يا له من يوم مثير! المزيد من المرح يأتي الأسبوع المقبل!
تعليقات
إرسال تعليق