ترجمة آلية تحت المراجعة
الأسبوع 4: الذاكرة
المصدر الأصلي على CS50المحاضرة الرابعة (Lecture 4)
- مرحبًا!
- فن البكسل
- سداسي عشري
- الذاكرة
- المؤشرات
- سلاسل
- المؤشر الحسابي
- مقارنة السلسلة
- النسخ والمالوك
- فالجريند
- القيم المهملة
- متعة المؤشر مع بينكي
- المبادلة
- تجاوز السعة
scanf- إدخال/إخراج الملف
- التلخيص
مرحبًا! (Welcome!)
- اليوم، قمنا بإزالة العديد من عجلات التدريب التي استخدمتها للبدء في هذا الفصل.
- تحدثنا في الأسابيع الماضية عن الصور التي يتم إنشاؤها من وحدات بناء أصغر تسمى البكسلات.
- سنتناول هذا الأسبوع مزيدًا من التفاصيل حول الأصفار والآحاد التي تتكون منها هذه الصور. وعلى وجه الخصوص، سوف نتعمق أكثر في اللبنات الأساسية التي تشكل الملفات، بما في ذلك الصور.
- علاوة على ذلك، سنناقش كيفية الوصول إلى البيانات الأساسية المخزنة في ذاكرة الكمبيوتر.
- بينما نبدأ اليوم، اعلم أن المفاهيم التي تتناولها هذه المحاضرة قد تستغرق بعض الوقت حتى يتم استيعابها بالكامل انقر.
فن البكسل (Pixel Art)
- وحدات البكسل عبارة عن مربعات، ونقاط فردية، ذات ألوان مرتبة على شبكة من الأعلى إلى الأسفل ومن اليسار إلى اليمين.
-
يمكنك تخيل الصورة كخريطة من البتات، حيث تمثل الأصفار اللون الأسود والآحاد تمثل اللون الأبيض.

سداسي عشري (Hexadecimal)
-
RGB، أو الأحمر، الأخضر، الأزرق، هي أرقام تمثل مقدار كل لون من هذه الألوان. في Adobe Photoshop، يمكنك رؤية هذه الإعدادات كما يلي:

لاحظ كيف أن كمية اللون الأحمر والأزرق والأخضر تغير اللون المحدد.
- يمكنك أن ترى من الصورة أعلاه أن اللون لا يتم تمثيله بثلاث قيم فقط. توجد في أسفل النافذة قيمة خاصة مكونة من أرقام وأحرف.
يتم تمثيل
255كـFF. لماذا قد يكون هذا؟ -
سداسي عشري هو نظام عد يحتوي على 16 قيمة عد. وهم على النحو التالي:
0 1 2 3 4 5 6 7 8 9 A B C D E Fلاحظ ذلك تم الآن إنشاء
Fيمثل15. - يُعرف أيضًا بالنظام الست عشري القاعدة 16.
- عند العد بالنظام الست عشري، يكون كل عمود قوة للرقم 16.
- الرقم
يتم تمثيل
0كـ00. - الرقم
يتم تمثيل
1كـ01. - الرقم
9يمثله09. - الرقم
يتم تمثيل
10كـ0A. - الرقم
يتم تمثيل
15كـ0F. - الرقم
يتم تمثيل
16كـ10. - الرقم
يتم تمثيل
255كـFF، لأن 16 × 15 (أوF) هو 240. أضف 15 أخرى لتحصل على 255. هذا هو أعلى رقم يمكنك حسابه باستخدام نظام سداسي عشري مكون من رقمين. - يعتبر النظام الست عشري مفيدًا لأنه يمكن تمثيله باستخدام أرقام أقل. يسمح لنا النظام الست عشري بتمثيل المعلومات بشكل أكثر إيجازًا.
الذاكرة (Memory)
-
في الأسابيع الماضية، ربما تتذكر عرض الفنان الخاص بنا لكتل الذاكرة المتزامنة. وبتطبيق الترقيم السداسي العشري على كل كتلة من كتل الذاكرة هذه، يمكنك تصورها على النحو التالي:

-
يمكنك أن تتخيل كيف قد يكون هناك ارتباك بشأن ما إذا كان قد تمثل الكتلة
10أعلاه موقعًا في الذاكرة أو القيمة10. وفقًا لذلك، وفقًا للاتفاقية، غالبًا ما يتم تمثيل جميع الأرقام السداسية العشرية بـ0xالبادئة كما يلي:
-
في نافذتك الطرفية، اكتب
code addresses.cواكتب الشيفرة الخاص بك كما يلي: يتم تخزين// Prints an integer #include <stdio.h> int main(void) { int n = 50; printf("%i\n", n); }لاحظ كيف
nفي الذاكرة بالقيمة50. -
يمكنك تصور كيفية تخزين هذا البرنامج لهذه القيمة كما يلي:

المؤشرات (Pointers)
-
تحتوي لغة C على عاملين قويين يتعلقان بالذاكرة:
& Provides the address of something stored in memory. * Instructs the compiler to go to a location in memory. -
يمكننا الاستفادة من هذه المعرفة عن طريق تعديل الشيفرة الخاص بنا على النحو التالي:
// Prints an integer's address #include <stdio.h> int main(void) { int n = 50; printf("%p\n", &n); }لاحظ
%p، والذي يسمح لنا بعرض عنوان موقع ما في الذاكرة.&nيمكن ترجمتها حرفيًا على أنها "عنوانn." سيؤدي تنفيذ هذا الرمز إلى إرجاع عنوان الذاكرة الذي يبدأ بـ0x. يمكنك تنزيل هذا الرمز هنا. - A المؤشر هو متغير يقوم بتخزين عنوان شيء ما. باختصار، المؤشر هو عنوان موجود في ذاكرة جهاز الكمبيوتر الخاص بك.
-
خذ بعين الاعتبار الشيفرة التالي:
int n = 50; int *p = &n;لاحظ ذلك تم الآن إنشاء
pهو مؤشر يحتوي على عنوان عدد صحيحn. -
قم بتعديل الشيفرة الخاص بك كما يلي:
// Stores and prints an integer's address #include <stdio.h> int main(void) { int n = 50; int *p = &n; printf("%p\n", p); }لاحظ أن هذا الشيفرة له نفس تأثير الشيفرة السابق. لقد استفدنا ببساطة من معرفتنا الجديدة بالـ
&و*عوامل التشغيل. يمكنك تنزيل هذا الرمز هنا. -
لتوضيح استخدام عامل تشغيل
*، ضع في اعتبارك ما يلي: يقوم سطر// Stores and prints an integer's address #include <stdio.h> int main(void) { int n = 50; int *p = &n; printf("%p\n", p); }لاحظ أن يتم استخدام كتلة
printfبطباعة عنوان العدد الصحيح. يقومint *pبإنشاء مؤشر مهمته تخزين عنوان الذاكرة لعدد صحيح. يمكنك تنزيل هذا الرمز هنا. -
يمكنك تصور الشيفرة الخاص بنا كما يلي:

لاحظ أن المؤشر يبدو كبيرًا نوعًا ما. في الواقع، عادةً ما يتم تخزين المؤشر كقيمة 8 بايت. يقوم
pبتخزين عنوان50. -
يمكنك تصور المؤشر بشكل أكثر دقة كعنوان يشير إلى عنوان آخر:

سلاسل (Strings)
- الآن بعد أن أصبح لدينا نموذج عقلي للمؤشرات، يمكننا استعادة مستوى التبسيط الذي تم تقديمه سابقًا في هذه الدورة.
-
قم بتعديل الشيفرة الخاص بك كما يلي:
// Prints a string #include <cs50.h> #include <stdio.h> int main(void) { string s = "HI!"; printf("%s\n", s); }لاحظ أن السلسلة تتم طباعة
s. يمكنك تنزيل هذا الرمز هنا. -
تذكر أن السلسلة هي مجرد مصفوفة من الأحرف. على سبيل المثال، يمكن تمثيل
string s = "HI!"على النحو التالي:
-
ومع ذلك، ما هو
sحقا؟ أين هوsمخزنة في الذاكرة؟ كما يمكنك أن تتخيل، يجب تخزينsفي مكان ما. يمكنك تصور العلاقةsإلى السلسلة كما يلي:
لاحظ كيف يتم استدعاء المؤشر
sيخبر المترجم بمكان وجود البايت الأول من السلسلة في الذاكرة. -
قم بتعديل الشيفرة الخاص بك كما يلي:
// Prints a string's address as well the addresses of its chars #include <cs50.h> #include <stdio.h> int main(void) { string s = "HI!"; printf("%p\n", s); printf("%p\n", &s[0]); printf("%p\n", &s[1]); printf("%p\n", &s[2]); printf("%p\n", &s[3]); }لاحظ أن ما ورد أعلاه يطبع مواقع الذاكرة لكل حرف في السلسلة
s. ال يُستخدم الرمز&لإظهار عنوان كل عنصر من عناصر السلسلة. عند تشغيل هذا الشيفرة، لاحظ أن العناصر0,1,2و3بجوار بعضها البعض في الذاكرة. يمكنك تنزيل هذا الرمز هنا. -
وبالمثل، يمكنك تعديل الشيفرة الخاص بك كما يلي:
// Declares a string with CS50 Library #include <cs50.h> #include <stdio.h> int main(void) { string s = "HI!"; printf("%s\n", s); }لاحظ أن هذا الرمز يقوم بإنشاء سلسلة باستخدام
cs50.h. يمكنك تنزيل هذا الرمز هنا. -
بخلع عجلات التدريب، يمكنك تعديل الشيفرة الخاص بك مرة أخرى: تمت إزالة
// Declares a string without CS50 Library #include <stdio.h> int main(void) { char *s = "HI!"; printf("%s\n", s); }لاحظ ذلك تم الآن إنشاء
cs50.h. يتم تنفيذ السلسلة كـchar *. سيقدم هذا الرمز السلسلة التي تبدأ من موقعs. يزيل هذا الشيفرة بشكل فعال عجلات التدريب الخاصة بـstringنوع البيانات المقدم بواسطةcs50.h. هذا كود C خام، بدون سقالات مكتبة cs50. يمكنك تنزيل هذا الرمز هنا. - يمكنك تخيل كيفية إنشاء سلسلة، كنوع بيانات.
- تعلمنا الأسبوع الماضي كيفية إنشاء نوع البيانات الخاص بك كبنية.
- تتضمن مكتبة cs50 تعريفًا للنوع كما يلي:
typedef char *string - تعريف النوع هذا، عند استخدام مكتبة cs50، هو تمثيل مبسط يسمح للشخص باستخدام نوع بيانات مخصص يسمى
string.
المؤشر الحسابي (Pointer Arithmetic)
- حساب المؤشر هو القدرة على إجراء العمليات الحسابية على مواقع الذاكرة.
-
يمكنك تعديل الشيفرة الخاص بك لطباعة كل موقع ذاكرة في السلسلة كما يلي:
// Prints a string's chars #include <stdio.h> int main(void) { char *s = "HI!"; printf("%c\n", s[0]); printf("%c\n", s[1]); printf("%c\n", s[2]); }لاحظ أننا نقوم بطباعة كل حرف في موقعه
s. يمكنك تنزيل هذا الرمز هنا. -
علاوة على ذلك، يمكنك تعديل الشيفرة الخاص بك كما يلي:
// Prints a string's chars via pointer arithmetic #include <stdio.h> int main(void) { char *s = "HI!"; printf("%c\n", *s); printf("%c\n", *(s + 1)); printf("%c\n", *(s + 2)); }لاحظ أن الحرف الأول في موقع تتم طباعة
s. ثم الشخصية في الموقع تتم طباعةs + 1، وهكذا. يمكنك تنزيل هذا الرمز هنا. -
وبالمثل، ضع في اعتبارك ما يلي:
// Prints substrings via pointer arithmetic #include <stdio.h> int main(void) { char *s = "HI!"; printf("%s\n", s); printf("%s\n", s + 1); printf("%s\n", s + 2); }لاحظ أن هذا الشيفرة يطبع القيم المخزنة في مواقع الذاكرة المختلفة بدءًا من
s. يمكنك تنزيل هذا الرمز هنا.
مقارنة السلسلة (String Comparison)
- سلسلة الأحرف هي ببساطة مجموعة من الأحرف يتم تحديدها بواسطة موقع البايت الأول الخاص بها.
-
في وقت سابق من الدورة، تناولنا المقارنة بين الأعداد الصحيحة. يمكننا تمثيل هذا في الشيفرة عن طريق الكتابة
code compare.cفي النافذة الطرفية كما يلي:// Compares two integers #include <cs50.h> #include <stdio.h> int main(void) { // Get two integers int i = get_int("i: "); int j = get_int("j: "); // Compare integers if (i == j) { printf("Same\n"); } else { printf("Different\n"); } }لاحظ أن هذا الشيفرة يأخذ عددين صحيحين من المستخدم ويقارنهما. يمكنك تنزيل هذا الرمز هنا.
- في حالة السلاسل، لا يمكن مقارنة سلسلتين باستخدام
عامل التشغيل
==. - الاستفادة من
سيحاول عامل التشغيل
==في محاولة لمقارنة السلاسل مقارنة مواقع الذاكرة الخاصة بالسلاسل بدلاً من الأحرف الموجودة فيها. وبناء على ذلك، نوصي باستخدامstrcmp. -
لتوضيح ذلك، قم بتعديل الشيفرة الخاص بك كما يلي:
// Compares two strings' addresses #include <cs50.h> #include <stdio.h> int main(void) { // Get two strings char *s = get_string("s: "); char *t = get_string("t: "); // Compare strings' addresses if (s == t) { printf("Same\n"); } else { printf("Different\n"); } }ملاحظة الكتابة
HI!لكلا السلسلتين لا يزال يؤدي إلى إخراجDifferent. يمكنك تنزيل هذا الرمز هنا. -
لماذا تبدو هذه السلاسل مختلفة؟ يمكنك استخدام ما يلي لتصور السبب:

- لذلك الشيفرة الخاص بـ
يحاول
compare.cأعلاه معرفة ما إذا كانت عناوين الذاكرة مختلفة، وليس السلاسل نفسها. -
باستخدام
strcmp، يمكننا تصحيح الشيفرة الخاص بنا: يمكن ل// Compares two strings using strcmp #include <cs50.h> #include <stdio.h> #include <string.h> int main(void) { // Get two strings char *s = get_string("s: "); char *t = get_string("t: "); // Compare strings if (strcmp(s, t) == 0) { printf("Same\n"); } else { printf("Different\n"); } }لاحظ ذلك تم الآن إنشاء
strcmpالعودة0إذا كانت السلاسل متماثلة. يمكنك تنزيل هذا الرمز هنا. -
لتوضيح كيفية تواجد هاتين السلسلتين في موقعين بشكل أكبر، قم بتعديل الشيفرة الخاص بك كما يلي:
// Prints two strings #include <cs50.h> #include <stdio.h> int main(void) { // Get two strings char *s = get_string("s: "); char *t = get_string("t: "); // Print strings printf("%s\n", s); printf("%s\n", t); }لاحظ كيف أنه لدينا الآن سلسلتين منفصلتين مخزنتين، على الأرجح في موقعين منفصلين. يمكنك تنزيل هذا الرمز هنا.
-
يمكنك رؤية مواقع هاتين السلسلتين المخزنتين مع تعديل بسيط: تم تغيير
// Prints two strings' addresses #include <cs50.h> #include <stdio.h> int main(void) { // Get two strings char *s = get_string("s: "); char *t = get_string("t: "); // Print strings' addresses printf("%p\n", s); printf("%p\n", t); }لاحظ أن يتم استخدام كتلة
%sإلى%pفي بيان الطباعة. يمكنك تنزيل هذا الرمز هنا.
النسخ والمالوك (Copying and malloc)
- هناك حاجة شائعة في البرمجة وهي نسخ سلسلة إلى أخرى.
-
في نافذتك الطرفية، اكتب
code copy.cواكتب الشيفرة كما يلي:// Capitalizes a string #include <cs50.h> #include <ctype.h> #include <stdio.h> #include <string.h> int main(void) { // Get a string string s = get_string("s: "); // Copy string's address string t = s; // Capitalize first letter in string t[0] = toupper(t[0]); // Print string twice printf("s: %s\n", s); printf("t: %s\n", t); }لاحظ ذلك تم الآن إنشاء
string t = sينسخ عنوانsإلىt. وهذا لا يحقق ما نرغب فيه. لم يتم نسخ السلسلة - تم نسخ العنوان فقط. وعلاوة على ذلك، لاحظ إدراجctype.h. يمكنك تنزيل هذا الرمز هنا. -
يمكنك تصور الشيفرة أعلاه كما يلي:

لاحظ ذلك تم الآن إنشاء
sوtما زالوا يشيرون إلى نفس كتل الذاكرة. هذه ليست نسخة أصلية من السلسلة. بدلاً من ذلك، هذان مؤشران يشيران إلى نفس السلسلة. -
قبل أن نتناول هذا التحدي، من المهم التأكد من أننا لا نواجه أي مشكلة خطأ في التجزئة من خلال الشيفرة الخاص بنا، حيث نحاول النسخ
string sإلىstring t، حيثstring tغير موجود. يمكننا توظيف تعملstrlenعلى النحو التالي للمساعدة في ذلك: يتم استخدام// Capitalizes a string, checking length first #include <cs50.h> #include <ctype.h> #include <stdio.h> #include <string.h> int main(void) { // Get a string string s = get_string("s: "); // Copy string's address string t = s; // Capitalize first letter in string if (strlen(t) > 0) { t[0] = toupper(t[0]); } // Print string twice printf("s: %s\n", s); printf("t: %s\n", t); }لاحظ ذلك تم الآن إنشاء
strlenللتأكدstring tلديه محتوى. إذا لم يحدث ذلك، فلن يتم نسخ أي شيء. يمكنك تنزيل هذا الرمز هنا. -
لكي نتمكن من عمل نسخة أصلية من السلسلة، سنحتاج إلى تقديم كتلتين أساسيتين جديدتين. أولا،
mallocيسمح لك، كمبرمج، بتخصيص كتلة بحجم معين من الذاكرة. ثانيا،freeيسمح لك بإخبار المترجم بذلك حرر تلك الكتلة من الذاكرة التي قمت بتخصيصها مسبقًا. -
يمكننا تعديل الشيفرة الخاص بنا لإنشاء نسخة أصلية من سلسلتنا كما يلي: يقوم
// Capitalizes a copy of a string #include <cs50.h> #include <ctype.h> #include <stdio.h> #include <stdlib.h> #include <string.h> int main(void) { // Get a string char *s = get_string("s: "); // Allocate memory for another string char *t = malloc(strlen(s) + 1); // Copy string into memory, including '\0' for (int i = 0; i <= strlen(s); i++) { t[i] = s[i]; } // Capitalize copy t[0] = toupper(t[0]); // Print strings printf("s: %s\n", s); printf("t: %s\n", t); }لاحظ ذلك تم الآن إنشاء
malloc(strlen(s) + 1)بإنشاء كتلة من الذاكرة بطول السلسلةsزائد واحد. وهذا يسمح بإدراج فارغة\0الحرف في السلسلة النهائية المنسوخة. ثم، تمر حلقةforعبر السلسلةsويقوم بتعيين كل قيمة لنفس الموقع على السلسلةt. يمكنك تنزيل هذا الرمز هنا. -
اتضح أن الشيفرة الخاص بنا غير فعال. قم بتعديل الشيفرة الخاص بك كما يلي: يتم تعريف
// Capitalizes a copy of a string, defining n in loop too #include <cs50.h> #include <ctype.h> #include <stdio.h> #include <stdlib.h> #include <string.h> int main(void) { // Get a string char *s = get_string("s: "); // Allocate memory for another string char *t = malloc(strlen(s) + 1); // Copy string into memory, including '\0' for (int i = 0, n = strlen(s); i <= n; i++) { t[i] = s[i]; } // Capitalize copy t[0] = toupper(t[0]); // Print strings printf("s: %s\n", s); printf("t: %s\n", t); }لاحظ ذلك تم الآن إنشاء
n = strlen(s)الآن في الجانب الأيسر منfor loop. من الأفضل عدم استدعاء الوظائف غير الضرورية في الحالة المتوسطة لـfor، حيث سيتم تشغيلها مرارًا وتكرارًا. عند التحركn = strlen(s)على الجانب الأيسر، الوظيفةstrlenيعمل مرة واحدة فقط. يمكنك تنزيل هذا الرمز هنا. -
ال يتم استخدام الدالة
Cتحتوي اللغة على وظيفة مضمنة لنسخ السلاسل التي تسمىstrcpy. ويمكن تنفيذها على النحو التالي:// Capitalizes a copy of a string using strcpy #include <cs50.h> #include <ctype.h> #include <stdio.h> #include <stdlib.h> #include <string.h> int main(void) { // Get a string char *s = get_string("s: "); // Allocate memory for another string char *t = malloc(strlen(s) + 1); // Copy string into memory strcpy(t, s); // Capitalize copy t[0] = toupper(t[0]); // Print strings printf("s: %s\n", s); printf("t: %s\n", t); }لاحظ ذلك تم الآن إنشاء
strcpyيقوم بنفس العمل الذي نقوم به تم تنفيذ حلقةforسابقًا. يمكنك تنزيل هذا الرمز هنا. -
كلاهما
get_stringوmallocالعودةNULL، قيمة خاصة في الذاكرة، في حالة حدوث خطأ ما. يمكنك كتابة رمز يمكنه التحقق من ذلك شرطNULLكالتالي:// Capitalizes a copy of a string without memory errors #include <cs50.h> #include <ctype.h> #include <stdio.h> #include <stdlib.h> #include <string.h> int main(void) { // Get a string char *s = get_string("s: "); if (s == NULL) { return 1; } // Allocate memory for another string char *t = malloc(strlen(s) + 1); if (t == NULL) { return 1; } // Copy string into memory strcpy(t, s); // Capitalize copy if (strlen(t) > 0) { t[0] = toupper(t[0]); } // Print strings printf("s: %s\n", s); printf("t: %s\n", t); // Free memory free(t); return 0; }لاحظ أنه إذا كانت السلسلة التي تم الحصول عليها ذات طول
0أو فشل malloc، تم إرجاعNULL. علاوة على ذلك، لاحظ ذلكfreeيتيح للكمبيوتر معرفة أنك انتهيت من كتلة الذاكرة هذه التي قمت بإنشائها عبرهاmalloc. يمكنك تنزيل هذا الرمز هنا.
فالجريند (Valgrind)
-
فالجريند هي أداة يمكنها التحقق لمعرفة ما إذا كانت هناك مشكلات متعلقة بالذاكرة في برامجك التي استخدمتها
malloc. على وجه التحديد، فإنه يتحقق لمعرفة ما إذا كنتfreeكل الذاكرة التي قمت بتخصيصها. -
خذ بعين الاعتبار الشيفرة التالي لـ
memory.c:// Demonstrates memory errors via valgrind #include <stdio.h> #include <stdlib.h> int main(void) { int *x = malloc(3 * sizeof(int)); x[1] = 72; x[2] = 73; x[3] = 33; }لاحظ أن تشغيل هذا البرنامج لا يسبب أية أخطاء. بينما يتم استخدام
mallocلتخصيص ذاكرة كافية لمصفوفة، ويفشل الشيفرة في ذلكfreeالتي خصصت الذاكرة. يمكنك تنزيل هذا الرمز هنا. - إذا كتبت
make memoryمتبوعة بـvalgrind ./memory، سوف تحصل على تقرير من valgrind that will report where memory has been lost as a result of your program. One error that valgrind reveals is that we attempted to assign the value of33فيxمن3، حيث قمنا بتخصيص مصفوفة من الحجم فقط3(x[0],x[1]وx[2]). خطأ آخر هو أننا لم نتحرر أبدًاx. -
يمكنك تعديل الشيفرة الخاص بك لتحرير ذاكرة
xعلى النحو التالي:// Demonstrates memory errors via valgrind #include <stdio.h> #include <stdlib.h> int main(void) { int *x = malloc(3 * sizeof(int)); x[0] = 72; x[1] = 73; x[2] = 33; free(x); }لاحظ أن تشغيل valgrind again now results in no memory leaks or errors
القيم المهملة (Garbage Values)
- عندما تطلب من المترجم كتلة من الذاكرة، ليس هناك ما يضمن أن هذه الذاكرة ستكون فارغة.
-
من المحتمل جدًا أن الذاكرة التي قمت بتخصيصها قد تم استخدامها مسبقًا بواسطة الكمبيوتر. وبناء على ذلك، قد ترى غير المرغوب فيه أو القيم المهملة. هذا نتيجة حصولك على كتلة من الذاكرة ولكنك لم تقم بتهيئتها. على سبيل المثال، ضع في اعتبارك التعليمة البرمجية التالية لـ
garbage.c:#include <stdio.h> #include <stdlib.h> int main(void) { int scores[1024]; for (int i = 0; i < 1024; i++) { printf("%i\n", scores[i]); } }لاحظ أنه سيتم تخصيص تشغيل هذا الرمز
1024مواقع في الذاكرة للصفيف الخاص بك، ولكن من المحتمل أن تُظهر حلقةforأنه ليست كل القيم موجودة0. من الأفضل دائمًا أن تكون على دراية بإمكانية القيم المهملة عندما لا تقوم بتهيئة كتل من الذاكرة إلى قيمة أخرى مثل الصفر أو غير ذلك. يمكنك تنزيل هذا الرمز هنا.
متعة المؤشر مع بينكي (Pointer Fun with Binky)
- شاهدنا أ فيديو من جامعة ستانفورد التي ساعدتنا على تصور المؤشرات وفهمها.
المبادلة (Swapping)
-
في العالم الحقيقي، هناك حاجة شائعة في البرمجة وهي تبديل قيمتين. بطبيعة الحال، من الصعب تبديل متغيرين دون مساحة تخزين مؤقتة. في الممارسة العملية، يمكنك الكتابة
code swap.cواكتب الشيفرة كما يلي لرؤية ذلك عمليًا:// Fails to swap two integers #include <stdio.h> void swap(int a, int b); int main(void) { int x = 1; int y = 2; printf("x is %i, y is %i\n", x, y); swap(x, y); printf("x is %i, y is %i\n", x, y); } void swap(int a, int b) { int tmp = a; a = b; b = tmp; }لاحظ أنه أثناء تشغيل هذا الرمز، فإنه لا يعمل. القيم، حتى بعد إرسالها إلى وظيفة
swap، لا تقم بالتبديل. لماذا؟ يمكنك تنزيل هذا الرمز هنا. - عندما تقوم بتمرير قيم إلى دالة، فإنك توفر نسخًا فقط. ال
يقتصر من
xوyعلى الوظيفة الرئيسية حيث تتم كتابة الشيفرة حاليًا. أي أن القيمxوyتم إنشاؤه في الشكل المجعد{}الأقواس من وظيفةmainلها نطاق فقط وظيفةmain. في الشيفرة الخاص بنا أعلاه،xوyيتم تمريرها قيمة . -
خذ بعين الاعتبار الصورة التالية:

لاحظ ذلك تم الآن إنشاء عالمي المتغيرات، التي لم نستخدمها في هذه الدورة، تعيش في مكان واحد في الذاكرة. يتم تخزين وظائف مختلفة في
stackفي منطقة أخرى من الذاكرة. -
الآن، تأمل الصورة التالية:

لاحظ ذلك تم الآن إنشاء
mainوswapلديهما قسمان منفصلان إطارات أو مناطق الذاكرة. لذلك، لا يمكننا ببساطة تمرير القيم من دالة إلى أخرى لتغييرها. -
قم بتعديل الشيفرة الخاص بك كما يلي:
// Swaps two integers using pointers #include <stdio.h> void swap(int *a, int *b); int main(void) { int x = 1; int y = 2; printf("x is %i, y is %i\n", x, y); swap(&x, &y); printf("x is %i, y is %i\n", x, y); } void swap(int *a, int *b) { int tmp = *a; *a = *b; *b = tmp; }لاحظ أنه لا يتم تمرير المتغيرات قيمة لكن بواسطة مرجع. أي أن عناوين يتم توفير
xوyللوظيفة. لذلك، يمكن لوظيفةswapمعرفة مكان إجراء التغييرات على الفعل الفعليxوyمن الوظيفة الرئيسية. يمكنك تنزيل هذا الرمز هنا. -
يمكنك تصور ذلك على النحو التالي:

تجاوز السعة (Overflow)
- A تجاوز سعة الكومة هو عندما تفيض الكومة، وتلمس مناطق من الذاكرة ليس من المفترض أن تفعلها.
- A تجاوز سعة المكدس يحدث عندما يتم استدعاء عدد كبير جدًا من الوظائف، مما يؤدي إلى تجاوز حجم الذاكرة المتوفرة.
- يتم أخذ كلا الأمرين في الاعتبار تجاوز سعة المخزن المؤقت.
scanf
- في CS50، قمنا بإنشاء وظائف مثل
get_intلتبسيط عملية الحصول على المدخلات من المستخدم. -
scanfهي وظيفة مضمنة يمكنها الحصول على مدخلات المستخدم. -
يمكننا إعادة التنفيذ
get_intيمكن استخدامه بسهولةscanfعلى النحو التالي:// Gets an int from user using scanf #include <stdio.h> int main(void) { int n; printf("n: "); scanf("%i", &n); printf("n: %i\n", n); }لاحظ أن قيمة يتم تخزين
nفي موقعnفي السطرscanf("%i", &n). يمكنك تنزيل هذا الرمز هنا. -
ومع ذلك، تحاول إعادة التنفيذ
get_stringليس بالأمر السهل. خذ بعين الاعتبار ما يلي:// Dangerously gets a string from user using scanf with array #include <stdio.h> int main(void) { char s[4]; printf("s: "); scanf("%s", s); printf("s: %s\n", s); }لاحظ أنه لا
&مطلوب لأن أسماء المصفوفات في لغة C تعمل كمؤشرات. ومع ذلك، لن يعمل هذا البرنامج بشكل صحيح في كل مرة يتم تشغيله. لا نخصص في أي مكان في هذا البرنامج ذاكرة كافية للسلسلة المقصودة. في الواقع، نحن لا نعرف كم من السلسلة يمكن للمستخدم إدخالها! علاوة على ذلك، نحن لا نعرف ما هي القيم المهملة التي قد تكون موجودة في موقع الذاكرة. يمكنك تنزيل هذا الرمز هنا. -
علاوة على ذلك، يمكن تعديل الشيفرة الخاصة بك على النحو التالي. ومع ذلك، يتعين علينا تخصيص قدر معين من الذاكرة مسبقًا لسلسلة ما:
// Using malloc #include <stdio.h> #include <stdlib.h> int main(void) { char *s = malloc(4); if (s == NULL) { return 1; } printf("s: "); scanf("%s", s); printf("s: %s\n", s); free(s); return 0; }لاحظ أنه إذا تم توفير سلسلة مكونة من أربع بايتات لك ربما يظهر خطأ.
-
من خلال تبسيط الشيفرة الخاص بنا على النحو التالي، يمكننا فهم هذه المشكلة الأساسية للتخصيص المسبق بشكل أكبر:
#include <stdio.h> int main(void) { char s[4]; printf("s: "); scanf("%s", s); printf("s: %s\n", s); }لاحظ أنه إذا قمنا بتخصيص مصفوفة من الحجم مسبقًا
4، يمكننا الكتابةcatووظائف البرنامج. ومع ذلك، سلسلة أكبر من هذا يمكن إنشاء خطأ. - في بعض الأحيان، قد يقوم المترجم أو النظام الذي يقوم بتشغيله بتخصيص ذاكرة أكبر مما نشير إليه. في الأساس، على الرغم من ذلك، فإن الشيفرة أعلاه غير آمن. لا يمكننا أن نثق في قيام المستخدم بإدخال سلسلة تناسب الذاكرة المخصصة لدينا مسبقًا.
إدخال/إخراج الملف (File I/O)
-
يمكنك القراءة من الملفات ومعالجتها. بينما ستتم مناقشة هذا الموضوع بشكل أكبر في الأسبوع المقبل، ضع في اعتبارك الشيفرة التالي لـ
phonebook.c:// Saves names and numbers to a CSV file #include <cs50.h> #include <stdio.h> #include <string.h> int main(void) { // Open CSV file FILE *file = fopen("phonebook.csv", "a"); // Get name and number char *name = get_string("Name: "); char *number = get_string("Number: "); // Print to file fprintf(file, "%s,%s\n", name, number); // Close file fclose(file); }لاحظ أن هذا الرمز يستخدم مؤشرات للوصول إلى الملف. يمكنك تنزيل هذا الرمز هنا.
- يمكنك إنشاء ملف يسمى
phonebook.csvقبل تشغيل الشيفرة أعلاه أو التنزيل phonebook.csv. بعد تشغيل البرنامج أعلاه وإدخال الاسم ورقم الهاتف، ستلاحظ أن هذه البيانات موجودة في ملف CSV الخاص بك. -
إذا أردنا التأكد من ذلك
phonebook.csvموجود قبل تشغيل البرنامج، يمكننا تعديل الشيفرة الخاص بنا على النحو التالي:// Saves names and numbers to a CSV file, checking for NULL #include <cs50.h> #include <stdio.h> #include <string.h> int main(void) { // Open CSV file FILE *file = fopen("phonebook.csv", "a"); if (file == NULL) { return 1; } // Get name and number char *name = get_string("Name: "); char *number = get_string("Number: "); // Print to file fprintf(file, "%s,%s\n", name, number); // Close file fclose(file); }لاحظ أن هذا البرنامج يحمي من أ
NULLالمؤشر عن طريق الاستدعاءreturn 1. يمكنك تنزيل هذا الرمز هنا. -
يمكننا تنفيذ برنامج النسخ الخاص بنا عن طريق الكتابة
code cp.cوكتابة الشيفرة كالتالي:// Copies a file #include <stdio.h> typedef unsigned char BYTE; int main(int argc, char *argv[]) { FILE *src = fopen(argv[1], "rb"); FILE *dst = fopen(argv[2], "wb"); BYTE b; while (fread(&b, sizeof(b), 1, src) != 0) { fwrite(&b, sizeof(b), 1, dst); } fclose(dst); fclose(src); }لاحظ أن هذا الملف يقوم بإنشاء نوع بيانات خاص بنا يسمى BYTE، وهو ما يعادل حجم uint8_t. ثم يقرأ الملف أ
BYTEويكتبه في ملف. يمكنك تنزيل هذا الرمز هنا. - تعتبر BMPs أيضًا مجموعة متنوعة من البيانات التي يمكننا فحصها ومعالجتها. هذا الأسبوع، ستفعل ذلك بالضبط في مجموعة المشكلات الخاصة بك!
التلخيص (Summing Up)
في هذا الدرس، تعرفت على المؤشرات التي توفر لك القدرة على الوصول إلى البيانات ومعالجتها في مواقع محددة بالذاكرة. وعلى وجه التحديد، تعمقنا في…
- فن البكسل
- سداسي عشري
- الذاكرة
- المؤشرات
- سلاسل
- المؤشر الحسابي
- مقارنة السلسلة
- النسخ
- مالوك وفالجريند
- القيم المهملة
- المبادلة
- تجاوز السعة
scanf- إدخال/إخراج الملف
نراكم في المرة القادمة!