[Powered by Google Translate] [סעיף 4 - יותר נוח]
[רוב אודן - אוניברסיטת הרווארד]
[זה CS50. - CS50.TV]
יש לנו מחר חידון, במקרה שאתם לא יודעים את זה.
זה בעצם על כל מה שיכל לראות בכיתה או היית צריך לראות אותו בכיתה.
הכולל מצביעים, למרות שהם נושא מאוד לאחרונה.
אתה צריך לפחות להבין את הרמות הגבוהות שלהם.
כל דבר שנעלם מעל בכיתה אתה צריך להבין לחידון.
אז אם יש לך שאלות עליהם, אתה יכול לשאול אותם עכשיו.
אבל זה הולך להיות מפגש מאוד של הסטודנט שבו אתם שואלים שאלות,
כך אני מקווה, לאנשים יש שאלות.
למישהו יש שאלות?
כן. >> [תלמיד] האם אתה הולך על מצביעים שוב?
אני מתכוון לעבור על מצביעים.
כל המשתנים שלך בהכרח לחיות בזיכרון,
אבל בדרך כלל אתה לא לדאוג בקשר לזה ואתה פשוט אומר x ו-y + 2 + 3
והמהדר להבין איפה הדברים חיים בשבילך.
ברגע שאתה מתעסק עם מצביעים, עכשיו אתה משתמש באופן מפורש כתובות זיכרון אלו.
אז משתנה אחד תהיה יחיד שגרים בכתובת אחת בכל זמן נתון.
אם אנחנו רוצים להצהיר מצביע, מה הסוג הולך להיראות כמו?
>> אני רוצה להכריז עמ מצביע. מה הסוג נראה לך?
[תלמיד] int * p. >> כן. אז int * p.
ואיך אני עושה את זה מצביע על x? >> [תלמיד] אמפרסנד.
[אודן] אז אמפרסנד נקרא פשוטו כמשמעו, את כתובתו של מפעיל.
לכן, כאשר אני אומר & x זה מקבל את כתובת הזיכרון של משתנה.
אז עכשיו יש לי p המצביע, ובכל מקום בקוד שלי אני יכול להשתמש * p
או שאני יכול להשתמש x וזה יהיה בדיוק אותו דבר.
(* P). מה זה עושה? מה כוכב שאומר?
זה [תלמיד] פירושו ערך בנקודה זו. >> כן.
אז אם אנחנו מסתכלים על זה, זה יכול להיות מאוד שימושי כדי לצייר את התרשימים
שבו זה קופסה קטנה של זיכרון עבור x, שקורית לי ערך 4,
אז יש לנו קופסה קטנה של זיכרון עבור p,
וכך p מצביע לx, אז אנו מפנים את חץ מ-p ל-x.
לכן, כאשר אנו אומרים * p אנחנו אומרים ללכת לתיבה שהיא p.
כוכב הוא לעקוב אחרי החץ ולאחר מכן לעשות מה שאתה רוצה שבתיבה ממש שם.
אז אני יכול לומר * p = 7, ושילך לתיבה שהיא x ושינוי שעד 7.
או שאני יכול לומר int z = * p * 2; זה מבלבל, כי כוכב זה, כוכב.
כוכב אחד הוא ביטול הפנית p, הכוכב השני הוא הכפלה פי 2.
שים לב שיכול להיות מוחלף ע '* באותה מידה עם x.
אתה יכול להשתמש בם באותה הדרך.
ואז בשלב מאוחר יותר יכול להיות לי נקודה p למשהו חדש לגמרי.
אני רק יכול לומר p = &z;
אז עכשיו p ללא נקודות ארוכות יותר לx; הוא מצביע על z.
וכל פעם שאני עושה * p שזה אותו הדבר כמו לעשות z.
אז הדבר השימושי על זה ברגע שנתחיל להיכנס לפונקציות.
>> זה סוג של טעם להכריז על מצביע שזה מצביע משהו
ואז אתה פשוט ביטול הפניה זה
כאשר אתה יכול להשתמש במשתנה המקורית מלכתחילה.
אבל כאשר אתה מקבל לתוך פונקציות - אז נגיד שיש לנו תפקיד כלשהו, ​​int foo,
שלוקח את מצביע ופשוט עושה * p = 6;
כמו שראינו קודם עם החלפה, אתה לא יכול לעשות החלפה יעילה ופונקציה נפרדת
רק על ידי מספרים שלמים, כי הכל עובר בC תמיד עובר ליד ערך.
גם כאשר אתה עובר מצביעים שאתה עובר ליד ערך.
זה פשוט כל כך קורה כי ערכים אלה הם כתובות זיכרון.
לכן, כאשר אני אומר foo (p); אני מעביר את המצביע לפונקצית foo
ואז הוא עושה foo * p = 6;
אז בתוך הפונקציה ש, * p הוא עדיין שווה הערך ל x,
אבל אני לא יכול להשתמש בחלק פנימי של x שהפונקציה כי זה לא scoped בתוך פונקציה.
אז * p = 6 הם הדרך היחידה שאני יכול לגשת למשתנה מקומית מפונקציה אחרת.
או, כן, מצביעים הם הדרך היחידה שאני יכול לגשת למשתנה מקומית מפונקציה אחרת.
[תלמיד] בואו נגיד שאתה רוצה לחזור מצביע. איך בדיוק אתה עושה את זה?
[אודן] תשואת מצביע כבמשהו כמו int y = 3; תשואה & y? >> [תלמיד] כן.
[אודן] אוקיי. אתה לא צריך לעשות את זה. זה רע.
אני חושב שראיתי בשקופיות הרצאות אלה שהתחלת לראות את כל תרשים זה של זיכרון
שבו עד כאן יש לך את הכתובת בזיכרון 0
וכאן יש לך 4 הופעות כתובת זיכרון או 2 עד 32.
אז יש לך כמה דברים ועוד כמה דברים ואז יש לך ערימה שלך
ויש לך ערימה שלך, שאתה רק התחלת ללמוד על, גדלת.
[תלמיד] האם לא הערימה מעל לערימה?
>> כן. הערימה היא בראש, לא? >> [תלמיד] ובכן, הוא הכניס 0 על גבי.
[תלמיד] הו, הוא הכניס 0 על גבי. >> [תלמיד] אה, בסדר.
הצהרה: כל מקום עם CS50 אתה הולך לראות את זה ככה. >> [תלמיד] אוקיי.
זה רק שכשאתה רואה ערימות ראשונה,
אוהב כשאתה חושב על ערימה אתה חושב על דברים לערום על גבי זו.
אז אנחנו נוטים להפוך את זה סביב כך הערימה גדלה כמו ערימה כרגיל
במקום הערימה משתלשל מטה. >> [תלמיד] לא ערימות טכניות לגדול מדי, אם כי?
זה תלוי במה אתה מתכוון בתגדל.
המחסנית והערימה תמיד לגדול בכיוונים מנוגדים.
ערימה תמיד גדלה בתחושה שהיא גדלה
כיוון כתובות גבוהות יותר זיכרון, והערימה הולכות וגדלה למטה
בכך שהוא גדל לכיוון כתובות זיכרון נמוכות יותר.
כך שהחלק העליון הוא 0 והתחתון הוא כתובות זיכרון גבוהה.
הם גדלו וגדלו, רק בכיוונים מנוגדים.
[תלמיד] אני פשוט התכוונתי שבגלל שאמרת לך לשים בתחתית ערימה
כי זה נראה יותר אינטואיטיבי, כי לערימה כדי להתחיל בראש ערימה,
הערימה של מעל עצמו יותר מדי, אז כלומר - >> כן.
ניתן גם לחשוב על הערימה כגדל ויותר גדולים, אבל יותר מכך המחסנית.
אז הערימה היא כזאת שאנחנו סוג של רוצים להראות שגדלו.
אבל בכל מקום אתה מסתכל אחרת הוא הולך להראות את הכתובה 0 בראש
ואת כתובת הזיכרון הגבוהה ביותר בתחתית, אז זה הנוף הרגיל שלך זיכרון.
>> האם יש לך שאלה?
[תלמיד] אתה יכול לספר לנו יותר על הגל?
כן. אני אגיע לזה בשנייה.
ראשית, יחזור למה חוזר & y הוא דבר רע,
במחסנית יש לך חבורה של מסגרות ערימה המייצגות את כל הפונקציות
שכבר קרא.
אז התעלמות דברים קודמים, החלק העליון של הערימה שלך תמיד תהיה הפונקציה העיקרית
מכיוון שזה התפקיד הראשון שהוא נקרא.
ואז כשאתה קורא לפונקציה אחרת, הערימה הולכת לגדול במורד.
אז אם אני קורא באיזה אירוע, foo, והיא מקבלת מסגרתו של המחסנית,
הוא יכול להתקשר לתפקיד כלשהו, ​​בר, היא מקבלת מסגרתו של המחסנית.
והבר יכול להיות רקורסיבית וזה יכול לקרוא לעצמו,
וכדי ששיחה שנייה לברים הוא הולך לקבל מסגרתו של המחסנית.
וכך מה שקורה במסגרות מחסנית אלה הם כל המשתנים המקומיים
ואת כל טענות פונקציה ש--
כל דברים שהם באופן מקומי scoped לתפקיד זה ללכת במסגרות מחסנית אלה.
אז זה אומר שכשאמרתי משהו כמו בר הוא פונקציה,
אני רק הולך להכריז על מספר שלם ולאחר מכן לחזור למצביע שהמספר השלם.
אז איפה זה y לחיות?
[התלמיד] y מתגורר בבר. >> [אודן] כן.
אי שם בריבוע קטן זה של זיכרון הוא ריבוע קטן יותר כי יש y בזה.
כשאני חוזר & y, אני מחזיר מצביע לגוש הקטן של זיכרון.
אבל אז כאשר חוזר לתפקד, מסגרתה מקבלת הערימה התפגרה המחסנית.
וזה למה קורא לערימה.
זה כמו במבנה נתוני המחסנית, אם אתה יודע מה זה.
או אפילו כמו ערימה של מגשים תמיד הדוגמא,
העיקרי היא הולך על הקרקעית, אז הפונקציה הראשונה שאתה קורא הולכת ללכת על גבי זה,
ואתה לא יכול לחזור לעיקרי עד שתחזור מכל הפונקציות שכבר נקראו
שהונח על גבי זה.
>> [תלמיד] אז אם אתה עשית לחזור & y, הערך שכפוף לשינוי ללא הודעה מוקדמת.
כן, זהו - >> [תלמיד] זה יכול להיות מוחלף. >> כן.
זה לגמרי - אם אתה מנסה ו--
זו תהיה גם בר * int כי זה חוזר מצביע,
כך סוג החזרתו הוא * int.
אם תנסה להשתמש בערך ההחזרה של פונקציה זו, זה התנהגות לא מוגדרת
כי המצביע שמצביע על זיכרון רע. >> [תלמיד] אוקיי.
אז מה אם, למשל, אתה הכרזת int * y = malloc (sizeof (int))?
זה יותר טוב. כן.
[תלמיד] דברנו על זה שאנחנו גוררים את הדברים לסל המחזור שלנו
הם לא באמת נמחקו, אנחנו פשוט מאבדים את המצביעים שלהם.
אז במקרה הזה אין לנו בעצם למחוק את הערך או שזה עדיין שם בזיכרון?
על פי רוב, זה הולך עדיין להיות שם.
אבל בואו נאמר שקורים לי להתקשר כמה תפקיד אחר, באז.
באז הוא הולך לקבל מסגרתו של המחסנית בפה.
זה הולך להיות להחליף את כל הדברים האלה,
ואז, אם כך לנסות ולהשתמש במצביע שקבלת בעבר,
זה לא הולך להיות אותו הערך.
זה הולך השתנה רק בגלל שקראת באז הפונקציה.
[תלמיד] אבל, אם אנחנו לא, האם היינו עדיין לקבל 3?
[אודן] סביר להניח, שהיית עושה.
אבל אתה לא יכול לסמוך על זה. C פשוט אומר התנהגות לא מוגדרת.
>> [תלמיד] הו, היא עושה. אוקיי.
לכן, כאשר אתה רוצה לחזור מצביע, זה המקום שבי malloc מגיע בשימוש.
אני כותב בעצם רק לחזור malloc (3 * sizeof (int)).
אנחנו נעבור על malloc יותר בשנייה, אבל הרעיון של malloc הוא כל המשתנים המקומיים שלך
תמיד הולכים במחסנית.
כל הדבר שהוא malloced ממשיך ערמה, וזה לנצח, ותמיד יהיה בערימה
עד שתשחרר אותה באופן מפורש.
אז זה אומר שכשמשהו שאתה malloc, זה הולך לשרוד לאחר שחוזר לתפקד.
[תלמיד] האם זה ישרוד לאחר התכנית מפסיקה לרוץ? מספר >>
אוקיי, אז זה הולך להיות שם עד שהתכנית עברה את כל הדרך עשתה בריצה. >> כן.
אנחנו יכולים לעבור על פרטים של מה קורה כאשר התכנית מפסיקה לרוץ.
ייתכן שתצטרך להזכיר לי, אבל זה דבר שונה לגמרי.
[תלמיד] אז malloc יוצר מצביע? >> כן.
Malloc - >> [תלמיד] אני חושב malloc מייעד גוש הזיכרון שמצביע יכול להשתמש.
[אודן] אני רוצה תרשים זה שוב. >> [תלמיד] אז פונקציה זו עובדת, אם כי?
[תלמיד] כן, malloc מייעד גוש הזיכרון שאתה יכול להשתמש בו,
ואז זה מחזיר את הכתובת של הבלוק הראשון של הזיכרון הזה.
>> [אודן] כן. לכן, כאשר אתם malloc, אתה תופס איזה בלוק של זיכרון
זה כרגע בערימה.
אם הערימה קטנה מדי, אז הערימה היא רק הולכת לגדול, והוא גדל בכיוון זה.
אז תניח הערימה קטנה מדי.
אז זה עומד לגדול קצת ולחזור מצביע לבלוק הזה שרק גדל.
כאשר דברים בחינם, אתה עושה יותר מקום בערמה,
כן אז מאוחר קורא לmalloc יכול לעשות שימוש חוזר בזיכרון שכבר שוחרר בעבר.
הדבר החשוב על malloc וחופשי הוא שזה נותן לך שליטה מלאה
במשך החיים של גושי הזיכרון הללו.
משתנים גלובליים הם תמיד בחיים.
משתנה מקומי חיים בתוך הטווח שלהם.
ברגע שאתה עובר על פני סד מתולתל, המשתנים המקומיים מתים.
זכרון Malloced חי כשאתה רוצה שהוא יהיה בחיים
ולאחר מכן הוא שוחרר כשאתה אומר לו להשתחרר.
אלה הם למעשה רק 3 סוגים של זיכרון, באמת.
יש ניהול זיכרון אוטומטי, המהווה את המחסנית.
דברים קורים לך באופן אוטומטי.
כשאתה אומר x int, זיכרון מוקצה עבור x int.
כאשר x יוצא מחוץ לתחום, זיכרון הוא קולח עבור x.
אז יש ניהול דינמי זיכרון, וזה מה שmalloc הוא,
וזה כאשר יש לך שליטה.
אתה דינמי להחליט מתי זיכרון צריך ולא צריך להיות מוקצה.
ואז יש סטטי, שרק אומר שהוא חי לנצח,
וזה מה שמשתנה גלובלי הם.
הם פשוט תמיד בזיכרון.
>> שאלות?
[תלמיד] אתה יכול להגדיר בלוק רק באמצעות סוגריים מסולסלים
אבל לא שיש לי אם הצהרה או הצהרה בזמן או משהו כזה?
באפשרותך להגדיר בלוק כמו בפונקציה, אבל שיש סוגריים מסולסלים מדי.
[תלמיד] אז אתה לא יכול פשוט להיות כמו זוג אקראי של סוגריים מסולסלים בקוד שלך
כי יש משתנים מקומיים? >> כן, אתה יכול.
בתוך בר int שנהיה לנו {int y = 3;}.
זה אמור להיות ממש כאן.
אבל זה לחלוטין מגדיר את היקף int y.
לאחר שהסד 2 המתולתל, y לא ניתן להשתמש יותר.
למרות שאתה כמעט אף פעם לא עושה את זה,.
אם אחזור למה שקורה כאשר תכנית מסתיימת,
יש סוג של שקר טעות / חצי שאנו נותנים במטרה רק כדי לעשות את הדברים קלים יותר.
אנחנו אומרים לך שכאשר אתה להקצות זיכרון
אתה הקצאת חלק הנתח של זכרון RAM לאותו משתנה.
אבל אתה לא באמת נוגע ישירות RAM אי פעם בתוכניות שלך.
אם אתה חושב על זה, איך אני ציירתי -
ובעצם, אם אתה עובר בGDB תראה אותו דבר.
לא משנה כמה פעמים אתה מפעיל התכנית שלך או מה תכנית שאתה מפעיל,
המחסנית תמיד עומדת להתחיל -
אתה תמיד הולך לראות משהו משתנה סביב oxbffff כתובת.
זה בדרך כלל אי ​​שם באזור זה.
אבל איך 2 תוכניות אולי יכולות להיות מצביעים לאותו הזיכרון?
[תלמיד] יש כמה ייעוד שרירותי של איפה oxbfff אמור להיות בזכרון RAM
שבעצם יכול להיות במקומות שונים בהתאם כאשר הפונקציה נקראה.
כן. מונח זיכרון וירטואלי.
הרעיון הוא שכל תהליך יחיד, כל תכנית יחידה שבו פועלת במחשב שלך
יש משלה - נניח 32 סיביים - כתובת החלל עצמאי לחלוטין.
זה מרחב הכתובות.
יש לה 4 ג'יגה העצמאית לחלוטין לשימוש.
>> אז אם אתה מפעיל 2 תוכניות בו זמנית, תכנית זו רואה 4 ג'יגה לעצמו,
תכנית זו רואה 4 ג'יגה לעצמו,
ואי אפשר שתכנית זו dereference מצביע
וסופו של דבר עם זיכרון מתכנית זו.
ומה זיכרון וירטואלי הוא הוא מיפוי ממרחב כתובות תהליכים
לדברים ממשיים בזכרון RAM.
כך שזה תלוי במערכת ההפעלה שלך כדי לדעת את זה,
היי, כשoxbfff מצביע זה הבחור dereferences, זה באמת אומר
שהוא רוצה RAM ייט 1000,
ואילו אם oxbfff dereferences תכנית זו, שהוא באמת רוצה זכרון RAM ייט 10000.
הם יכולים להיות רחוקים אחד מהשני באופן שרירותי.
זה נכון אפילו לגבי דברים בתוך מרחב כתובות תהליכים בודד.
אז כמו שהוא רואה את כל 4 ג'יגה לעצמו, אבל בואו יגיד -
[תלמיד] האם כל תהליך אחד -
בואו נגיד שיש לך מחשב עם רק 4 גיגהבייט של זכרון RAM.
האם כל תהליך יחיד לראות את 4 ג'יגה השלמה? >> כן.
אבל 4 ג'יגה היא רואה היא שקר.
זה פשוט שהוא חושב שיש את כל הזיכרון הזה כי הוא לא יודע שום תהליך אחר קיים.
זה יהיה להשתמש רק כזיכרון כמה שזה באמת צריך.
מערכת ההפעלה היא לא הולכת לתת לזכרון RAM לתהליך זה
אם הוא לא משתמש בכל זיכרון בכל האזור הזה.
זה לא הולך לתת לו זיכרון עבור אזור זה.
אבל הרעיון הוא ש-- אני מנסה לחשוב על - אני לא יכול לחשוב על אנלוגיה.
אנלוגיות הן קשות.
אחת הסוגיות של זיכרון וירטואלי או אחד מהדברים שזה פתרון
הוא שתהליכים צריכים להיות לגמרי לא מודעים לזה.
ואז אתה יכול לכתוב כל תכנית שרק dereferences כל מצביע,
רוצים פשוט לכתוב תכנית שאומרת * (ox1234),
ושהכתובת של ביטול הפנית זכרון 1234.
>> אבל זה תלוי במערכת ההפעלה ואז לתרגם את מה ש1234 אמצעים.
אז אם 1234 קורים להיות כתובת זיכרון תקף לתהליך זה,
כמו שזה בערימה או משהו, אז זה יחזיר את הערך של כתובת זיכרון
ככל התהליך יודע.
אבל אם 1234 אינן כתובת חוקית, כמו שזה קורה לנחות
באיזו חלקה קטנה של זיכרון כאן שהיא מעבר לערימה ומעבר לערימה
ואתה לא ממש נצלת את זה, אז זה כאשר אתה מקבל דברים כמו segfaults
בגלל שאתה נוגע בזיכרון שאתה לא צריך להיות נוגע ללב.
זה נכון גם -
מערכה 32-bit, 32 סיביות אומרת שאתה צריך 32 ביטים כדי להגדיר כתובת זיכרון.
זאת הסיבה שהמצביעים הם 8 בתים כי 32 ביטים הם 8 בתים - או 4 בתים.
מצביעים הם 4 בתים.
אז כשאתה רואה את סמן כמו oxbfffff, כלומר -
בתוך כל תכנית נתנה לך פשוט יכול לבנות כל מצביע שרירותי,
בכל מקום מox0 ל8 f's השור - FFFFFFFF.
[תלמיד] לא אמרו שהם 4 בתים? >> כן.
[תלמיד] ואז כל בית יהיה - >> [אודן] הקסדצימאלי.
הקסאדצימלי - 5, 6, 7, 8. אז מצביעים שאתה הולך לראות תמיד בהקסדצימלי.
זה רק איך אנו מסווגים מצביעים.
כל 2 ספרות של הקסדצימלי היא בית 1.
אז הנה הולך להיות 8 ספרות הקסדצימליים עבור 4 בתים.
אז כל מצביע בודד על מערכה 32-bit הולך להיות 4 בתים,
מה שאומר שבתהליך שלך אתה יכול לבנות את כל 4 בתים שרירותיים
ולהפוך את מצביע מחוץ לזה,
מה שאומר שככל שזה מודע, זה יכול לתת מענה לכל 2 ל32 הבתים של זיכרון.
למרות שזה לא ממש יש גישה לזה,
גם אם המחשב שלך יש 512 מגה בייט בלבד, הוא חושב שיש כל כך הרבה זיכרון.
ומערכת ההפעלה היא חכמה מספיק שזה יהיה להקצות רק את מה שאתה באמת צריך.
זה לא פשוט ללכת, הו, תהליך חדש: 4 הופעות.
>> כן. >> [תלמיד] מה השור מתכוון? למה אתה כותב את זה?
זה רק הסמל להקסדצימלי.
כשאתה רואה את התחלה עם מספר שור, הדברים הרצופים הם הקסדצימלי.
[תלמיד] היית להסביר על מה קורה כאשר תכנית מסתיימת. >> כן.
מה קורה כאשר תכנית מסתיימת הוא מערכת ההפעלה
פשוט מחק את המיפויים שיש לה לכתובות אלה, וזהו זה.
מערכת ההפעלה עכשיו יכולה פשוט לתת לזיכרון שלתכנית אחרת לשימוש.
[תלמיד] אוקיי.
לכן, כאשר אתה מקצה משהו על הערימה או משתני המחסנית או כל דבר או גלובליים,
כולם פשוט ייעלמו ברגע שהתכנית מסתיימת
מכיוון שמערכת ההפעלה היא עכשיו חופשיה לתת זיכרון לכל תהליך אחר.
[תלמיד] למרות שיש כנראה עדיין ערכים שנכתבו ב? >> כן.
הערכים צפויים עדיין שם.
זה פשוט שזה הולך להיות קשה להגיע אליהם.
זה הרבה יותר קשה להגיע אליהם, מאשר לקבל בקובץ שנמחק
בגלל סוג הקובץ שנמחק מיושב שם במשך זמן רב ואת הכונן הקשיח הוא הרבה יותר גדול.
אז זה הולך להחליף חלקים שונים של זיכרון
לפני שזה קורה כדי לדרוס את הנתח של זיכרון שהקובץ שהיה אמור להיות בו.
אבל זיכרון ראשי, זיכרון RAM, לעבור הרבה יותר מהר,
אז זה הולך מהר מאוד להיות מוחלף.
שאלות על זה או כל דבר אחר?
[תלמיד] יש לי שאלות על נושא אחר. >> אוקיי.
האם יש למישהו שאלות על זה?
>> אוקיי. נושא אחר. >> [תלמיד] אוקיי.
עובר עליי כמה מהבדיקות בפועל,
ובאחד מהם הוא מדבר על sizeof
ואת הערך שהיא מחזירה או סוגים שונים משתנים. >> כן.
והוא אמר שגם int וארוכה גם יחד תשואת 4, כך שהם עוד שני 4 הבתים.
האם יש הבדל בין int וארוך, או שזה אותו הדבר?
כן, יש הבדל.
C הסטנדרטי -
אני כנראה הולך לעשות בלגן.
C הסטנדרטי הוא בדיוק כמו מה C הוא, התיעוד הרשמי של ג
זה מה שזה אומר.
אז C הסטנדרטי פשוט אומר שchar לנצח ותמיד יהיה בית 1.
כל מה שאחרי - קצר היא תמיד רק הוגדר כגדול או שווה ל char.
זה עשוי להיות בהחלט יותר מאשר, אבל לא חיובי.
Int הוא פשוט מוגדר כגדול או שווה לקצר.
והוא ממש הגדיר עוד להיות גדול או שווה ל int.
ועוד ארוכים הוא גדול או שווה לזמן רב.
אז הדבר היחיד C הסטנדרטי מגדיר הוא סדר היחסי של כל דבר.
הסכום בפועל של זכרון דברים שהוא בדרך כלל לקחת עד יישום,
אבל זה מוגדר די טוב בשלב זה. >> [תלמיד] אוקיי.
אז מכנסיים קצרים כמעט תמיד הולכים להיות 2 בתים.
Ints כמעט תמיד הולך להיות 4 בתים.
משתוקק ארוך כמעט תמיד הולכים להיות 8 בתים.
ומתגעגע, זה תלוי אם אתה משתמש 32-bit או 64-bit מערכת.
אז עוד הולך מתאימים לסוג של מערכת.
אם אתה משתמש במערכת 32-bit כמו המכשיר, זה הולך להיות 4 בתים.
אם אתה משתמש ב 64-bit כמו הרבה מחשבים האחרונים, זה הולך להיות 8 בתים.
>> Ints הם כמעט תמיד 4 בתים בשלב זה.
משתוקק ארוך הם כמעט תמיד 8 בתים.
בעבר, ints פעם היה רק ​​2 בתים.
אבל שם לב שזה עומד בכל היחסים האלה של יותר מ ושווים לחלוטין.
כל עוד מותר בהחלט להיות באותו הגודל כמו מספר שלם,
וזה גם אפשר להיות באותו הגודל כמו זמן ארוך.
וזה פשוט כל כך קורה להיות שב99.999% ממערכות, שזה הולך להיות שווה
או int או ארוך ארוך. זה פשוט תלוי ב32-bit או 64-bit. >> [תלמיד] אוקיי.
בצף, איך היא הנקודה העשרונית המיועדת במונחים של ביטים?
אוהב כמו בינארי? >> כן.
אתה לא צריך לדעת את זה לCS50.
אתה אפילו לא לומד את זה בשנת 61.
אתה לא לומד את זה באמת בכל קורס.
זה רק ייצוג.
אני שוכח את הקצבאות הסיביות המדויקות.
הרעיון של נקודה צפה הוא שאתה להקצות מספר מסוים של ביטים לייצג -
בעיקרון, כל מה שהוא בייצוג מדעי.
אז אתה להקצות מספר מסוים של ביטים כדי לייצג את המספר עצמו, כמו 1.2345.
אני לא יכול לייצג את מספר בספרות של יותר מ 5.
ואז אתה גם להקצות מספר מסוים של ביטים כך שהוא נוטה להיות כמו
אתה רק יכול ללכת עד למספר מסוים, כמו זה המעריך הגדול ביותר שאתה יכול להיות,
ואתה יכול לרדת רק למעריך מסוים,
רוצה שהמעריך הקטן ביותר שאתה יכול לקבל.
>> אני לא זוכר את קטעי הדרך המדויקות מוקצים לכל הערכים הללו,
אך מספר מסוים של ביטים מוקדשים ל1.2345,
עוד מספר מסוים של ביטים מוקדשים לפרשן,
וזה אפשרי רק כדי לייצג את מעריך בגודל מסוים.
[תלמיד] וכפול? האם זה כמו מצוף ארוך במיוחד? >> כן.
זה אותו הדבר כמו לצוף רק שעכשיו אתה בתים של 8 במקום 4 בתים.
כעת תוכל להשתמש ב9 ספרות או 10 ספרות,
וזה יהיה מסוגל ללכת עד 300 במקום 100. >> [תלמיד] אוקיי.
ומרחף גם 4 בתים. >> כן.
ובכן, שוב, זה כנראה תלוי ביישום כולל כללי,
אבל צף הם 4 בתים, זוגות הם 8.
זוגות נקראים כפול משום שהם כפולים בגודל של מצופים.
[תלמיד] אוקיי. ויש פעמים שמכפילים? >> יש שלא.
אני חושב - >> [תלמיד] כמו משתוקק ארוך? >> כן. לא נראה לי. כן.
[תלמיד] במבחן של השנה שעברה לא הייתה שאלה לגבי הפונקציה העיקרית
צורך להיות חלק מהתכנית שלך.
התשובה הייתה שזה לא צריך להיות חלק מהתכנית שלך.
באיזה מצב? זה מה שראיתי.
[אודן] נראה - >> [תלמיד] מה מצב?
האם יש לך בעיה? >> [תלמיד] כן, אני בהחלט יכול למשוך אותו.
זה לא חייב להיות, מבחינה טכנית, אבל בעיקרון זה הולך להיות.
[תלמיד] ראיתי אחד בשנה של שונה.
זה היה כמו אמת או שקר: חוקי - >> אה, קובץ c.?
. [תלמיד] כל קובץ c צריך להיות - [גם כשדבר בפעם אחת - לא מובן]
אוקיי. אז זה נפרד.
>> . קובץ c פשוט צריך להכיל פונקציות.
אתה יכול לקמפל קובץ לתוך קוד מכונה, בינארי, שיהיה,
זה בלי שיהיה הפעלה עדיין.
הפעלה תקפה חייבת להיות פונקציה עיקרית.
אתה יכול לכתוב 100 פונקציות בקובץ 1 אבל לא עיקרית
ולאחר מכן לאסוף את שלינארי,
אז אתה כותב קובץ אחר שיש רק מלך אלא שהיא ממכנת חבורה של פונקציות אלה
בקובץ בינארי הזה כאן.
ולכן כאשר אתה עושה הפעלה, זה מה שעושה המקשר
הוא שהוא משלב 2 קבצים בינאריים אלה להפעלה.
אז. קובץ c לא צריך להיות פונקציה עיקרית בכלל.
ועל בסיס קוד גדול שתראה אלף. קבצי C וקבצים עיקריים 1.
עוד שאלות?
[תלמיד] הייתה שאלה אחרת.
זה אומר לעשות הוא מהדר. אמת או שקר?
והתשובה הייתה שקרי, והבנתי למה זה לא כמו קלאנג.
אבל מה שאנחנו קוראים לעשות אם זה לא?
הפוך הוא בעצם רק - אני יכול לראות בדיוק מה שהיא ממכנת אותו.
אבל זה פשוט מפעיל פקוד.
הפוך.
אני יכול למשוך את זה. כן.
אה, כן. הפוך גם עושה את זה.
זה אומר, שהמטרה של כלי האיפור היא לקבוע באופן אוטומטי
שחתיכות של תכנית גדולה צריכות להיות recompiled
ולהוציא את הפקודות להדר אותם.
אתה יכול לעשות להפוך את קבצים שנמצאים ממש ענקים.
הפוך מסתכל על חותמות הזמן של קבצים, וכמו שאמרנו קודם,
אתה יכול לקמפל את קבצים בודדים, וזה לא עד שתגיע למקשר
שהם ביחד להפעלה.
אז אם יש לך 10 קבצים שונים וביצוע שינוי עד 1 מתוכם,
אז מה עושה, היא הולכת לעשות הוא פשוט קומפילציה שקובץ 1
ולאחר מכן לקשר מחדש את הכל ביחד.
אבל זה הרבה יותר מטומטם מזה.
זה תלוי בך כדי להגדיר לגמרי שזה מה שצריך לעשות.
זה כברירת מחדל יש את היכולת לזהות חומר חותמת הזמן הזה,
אבל אתה יכול לכתוב קובץ לעשות כדי לעשות משהו.
אתה יכול לכתוב להפוך את הקובץ, כך שכאשר אתה מקליד לעשות את זה רק תקליטורים לספרייה אחרת.
אני היה מתוסכל כי אני כל טקטיקה הפנימית של המכשיר שלי
ואז אני רואה את ה-PDF מהמק.
>> אז אני הולך לFinder ואני יכול לעשות לך, להתחבר לשרת,
והשרת שאני מתחבר למכשיר הוא שלי, ואז אני פותח את ה-PDF
שמקבל שהוכן על ידי LaTeX.
אבל אני היה מתוסכל, כי בכל פעם שהייתי צריך לרענן את ה-PDF,
הייתי צריך להעתיק אותו לספרייה מסוימת שזה יכול לגשת
וזה אהיה מעצבן.
אז במקום זה כתב קובץ לעשות, שיש לך להגדיר איך הוא עושה את הדברים.
איך אתה עושה את זה בפורמט PDF-LaTeX.
בדיוק כמו כל קובץ אחר לעשות - או שאני מניח שאתה לא ראית את הקבצים לעשות,
אבל יש לנו במכשיר קובץ יצרן עולמי שרק אומר,
אם אתה מקים קובץ C, השתמש קלאנג.
וכן כאן בקובץ המעשה שלי שאני עושה אני אומר,
זה קובץ שאתה הולך רוצה לקמפל עם PDF-LaTeX.
ואז זה LaTeX PDF שעושה קומפילציה.
הפכו לא הידור. זה פשוט פועל בפקודות אלה ברצף שצוינתי.
אז הוא פועל LaTeX-PDF, הוא מעתיק אותו לספרייה אני רוצה שזה יועתק ל,
תקליטור זה לספרייה ועושה דברים אחרים,
אך כל שהיא עושה הוא לזהות כאשר שינויים בקבצים,
ואם היא משתנית, אז זה יהיה להפעיל את הפקודות שהוא אמור לרוץ
כאשר השינויים בקבצים. >> [תלמיד] אוקיי.
אני לא יודע איפה את הקבצים להפוך את העולם הם בשבילי כדי לבדוק את זה.
שאלות אחרות? שום דבר מהעבר חידונים? כל דברים מצביעים?
יש דברים עדינים עם מצביעים כמו -
אני לא הולך להיות מסוגל למצוא שאלת חידון על זה -
אבל בדיוק כמו שדברים מהסוג הזה.
ודא שאתה מבין, שכאשר אני אומר int * x * y -
זה לא בדיוק משהו כאן, אני מניח.
אבל כמו * x * y, אלה 2 משתנים שנמצאים במחסנית.
כשאני אומר x = malloc (sizeof (int)), x הוא עדיין משתנה במחסנית,
malloc היא קצת מעל בלוק בערימה, ואנחנו מנהלים נקודת x לערימה.
>> אז משהו על נקודתי המחסנית לערימה.
בכל פעם שכל דבר שאתה malloc, אתה בהכרח האחסון הפנימי של מצביע.
אז המצביע שנמצא במחסנית, בלוק malloced הוא בערימה.
הרבה אנשים מתבלבלים ואומרים int * x = malloc, x הוא בערימה.
לא, מה x מצביע על הערימה.
x עצמו בערימה, אלא אם מהסיבה כלשהי יש לך x להיות משתנה גלובלית,
ובמקרה זה במקרה באזור אחר של זיכרון.
אז שמירה על מסלול, דיאגרמות קופסה והחץ האלה הן די שכיחות לחידון.
או אם זה לא בחידון 0, זה יהיה ביום 1 בחידון.
אתה צריך לדעת את כל אלה, את השלבים בהידור
מאז שיש לך לענות על שאלות באלה. כן.
[תלמיד] נוכל ללכת על הצעדים האלה - >> בטח.
לפני מדרגות והידור יש לנו preprocessing,
איסוף, הרכבה, וקישור.
Preprocessing. מה זה עושה?
זה צעד הקל ביותר ב-- טוב, לא כמו -
זה לא אומר שזה צריך להיות מובן מאליו, אבל זה צעד הקל ביותר.
אתם יכולים ליישם את זה בעצמכם. כן.
[תלמיד] קח את מה שיש לך כולל בכך והיא מעתיקה ולאחר מכן גם מגדירה.
זה נראה לדברים כמו # # כולל ולהגדיר,
וזה רק עותקים ומשחות מה אלו בעצם אומרים.
אז כשאתה אומר # כולל cs50.h, בעיבוד המוקדם הוא העתקה והדבקת cs50.h
אל קו זה.
כשאתה אומר # להגדיר x להיות 4, בעיבוד המוקדם עובר את התכנית כולה
ומחליף את כל המופעים של x עם 4.
אז preprocessor לוקח קובץ C תקף ופלטי קובץ C תקף
שבו הדברים היו להעתיק ולהדביק.
אז עכשיו קומפילציה. מה זה עושה?
[תלמיד] זה הולך מג' לינארי.
>> [אודן] זה לא הולך כל הדרך לינארי.
[תלמיד] לקוד מכונה אז? >> זה לא קוד מכונה.
[תלמיד] עצרת? >> אסיפה.
זה הולך לאסיפה לפני שהוא הולך כל הדרך לקוד C,
ובמרבית השפות לעשות משהו כזה.
פיק כל שפה ברמה גבוהה, ואם אתה הולך להדר אותו,
סביר להניח להדר בצעדים.
ראשון זה הולך לקמפל פייטון לC, אז זה הולך לקמפל C לעצרת,
ולאחר מכן עצרת הולכת מתורגמת לינארי.
אז הקומפילציה הולכת להביא אותו מג' לעצרת.
מילת הידור פירושו בדרך כלל מביאה אותו מרמה גבוהה יותר
לשפת תכנות ברמה נמוכה יותר.
אז זה הצעד היחיד באוסף שבו אתה מתחיל עם שפה ברמה גבוהה
וסופו של דבר בשפה ברמה נמוכה, ולכן הצעד נקרא הידור.
[תלמיד] במהלך הקומפילציה, יניח שעשית # include cs50.h.
האם מהדר קומפילצית cs50.h, כמו הפונקציות הניתנות לשם,
ולתרגם את זה לקוד של עצרת, כמו גם,
או שזה יהיה להעתיק ולהדביק משהו שהיה לפני העצרת?
cs50.h יהיה די לא בסופו באסיפה.
דברים כמו אבות טיפוס לפונקציות ודברים הם רק בשביל שתהיו זהיר.
היא מבטיחה שהמהדר יכול לבדוק דברים כמו שאתה קורא פונקציות
עם סוגי ההחזרה הנכונים ואת הטיעונים ודברים הנכונים.
>> אז cs50.h יהיה מעובד לקובץ, ולאחר מכן כאשר זה הידור
זה בעצם זרק אותו אחרי זה מוודא כי הכל הוא להיקרא בצורה נכונה.
אבל את הפונקציות שהוגדרו בCS50 הספרייה, שהם נפרדים מcs50.h,
אלה לא נערכו בנפרד.
כי בעצם ירד בשלב הקישור, כדי שנגיע לזה בשנייה.
אבל קודם, מה הרכבה?
[תלמיד] עצרת לינארי? >> כן.
הרכבה.
אנחנו לא קוראים לזה הידור כי עצרת היא די תרגום טהור בינארי.
יש היגיון במעט מאוד הולך מעצרת לינארי.
זה בדיוק כמו להסתכל בטבלה, הו, יש לנו הוראה זו;
מתאים לינארי 01110.
וכן את הקבצים שהרכבה כלל תפוקות. קבצי o.
ו. קבצי o הם מה שאנחנו אומרים זה לפני,
איך קובץ לא צריך להיות פונקציה עיקרית.
כל קובץ יכול להיות הידור עד. קובץ o כל עוד זה קובץ C תקף.
זה יכול להיות הידור עד. O.
עכשיו, הקישור הוא מה שבעצם מביא חבורה של. קבצי o ומביא אותם לביצוע.
אז מה עושה מקשר הוא שאתה יכול לחשוב ספריית CS50 כ. קובץ O.
זה קובץ בינארי שכבר נאסף.
ולכן כאשר אתה לקמפל את הקובץ, hello.c, שקורא GetString,
hello.c מקבל הידור עד hello.o,
hello.o הוא עכשיו בינארי.
היא משתמשת GetString, כך שהוא צריך ללכת אל cs50.o,
והמקשר smooshes יחד ומעתיק GetString לקובץ זה
ויוצא עם הרצה שיש את כל הפונקציות שהוא צריך.
אז cs50.o הוא לא באמת קובץ פלט, אבל זה מספיק קרוב, שאין הבדל מהותי.
כך שחיבור פשוט מביא את החבורה של קבצים יחד
כך בנפרד מכילים את כל הפונקציות שאני צריך להשתמש
ויוצר להרצה שתהיה ממש לרוץ.
>> וכך זה גם מה שאנחנו אומרים זה לפני
שם אתה יכול לקבל 1000. קבצי C, אתה לרכז את כולם ל. קבצי o,
כנראה שייקח קצת זמן, ואז אתה משנה 1. קובץ c.
אתה רק צריך להדר מחדש ש1. קובץ c ואז הכל לקשר מחדש אחר,
לקשר את הכל בחזרה ביחד.
[תלמיד] כשאתה מקשר אנו כותבים lcs50?
כן, מה שlcs50. כי אותות הדגל למקשר כי אתה צריך להיות קישור שבספרייה.
שאלות?
האם עבר על ינארי האחר מ 5 שניות כי בהרצאה הראשונה?
לא נראה לי.
אתה צריך לדעת את כל הגדול אוס שאנחנו מתרחקים יותר,
ואתה צריך להיות מסוגל, אם נתתי לך פונקציה,
אתה אמור להיות מסוגל לומר שזה גדול O, בערך. או גם, הגדול O הוא מחוספס.
אז אם אתה רואה לקנן לולאות הופנה על אותו המספר של דברים,
כמו int i, i <n; int j, j > [התלמיד] n בריבוע. >> זה נוטה להיות n בריבוע.
אם מקונן משולש, הוא נוטה להיות n חתוך לקוביות.
אז זה סוג של דבר שאתה צריך להיות מסוגל להצביע באופן מיידי.
אתה צריך לדעת את סוג כניסה ומיון בועות ולמזג מיון וכל אלה.
זה קל יותר להבין מדוע הם אלה n בריבוע וn log n וכל זה
משום שאני חושב שהייתה בחידון שנה שבו אנחנו בעצם נתנו לך
יישום של מיון בועות ואמר, "מהו זמן הריצה של פונקציה זו?"
אז אם אתה מזהה את זה כסוג של בועה, אז אתה יכול להגיד n בריבוע באופן מיידי.
אבל אם אתה רק מסתכל על זה, אתה אפילו לא צריך להבין מיון בועות זה;
אתה יכול רק להגיד שזה עושה את זה ואת זה. זה n בריבוע.
[תלמיד] האם יש דוגמאות קשות אתה יכול לבוא עם,
כמו רעיון דומה של להבין?
>> אני לא חושב שהייתי נותן לך את כל דוגמאות קשות.
דבר מיון הבועות הוא כ קשוח כמונו הייתי הולכים,
וגם זה, כל עוד אתה מבין שאתה iterating על המערך
לכל רכיב במערך, שהולך להיות משהו שn בריבוע.
יש שאלות כלליות, כמו זכות שיש לנו כאן - הו.
רק לפני כמה ימים, דאג טען, "המצאתי אלגוריתם שיכול למיין מערך
"מספרים של n בO (logn) זמן!"
אז איך אנחנו יודעים שזה בלתי אפשרי?
[תגובת תלמיד לא נשמעה] >> כן.
לכל הפחות, יש לך לגעת בכל אחד מרכיבים במערך,
אז אי אפשר למיין מערך של -
אם הכל כדי לא ממוין, ואז אתה הולך להיות נוגע בכל דבר במערך,
כך שזה בלתי אפשרי לעשות את זה בפחות מ O של n.
[תלמיד] אתה הראית לנו דוגמה של להיות מסוגל לעשות את זה ב O של n
אם אתה משתמש הרבה בזיכרון. >> כן.
וזהו זה - אני לא זוכר מה כלומר - האם זה מיון מנייה?
הממ. זה אלגוריתם מיון של מספרים שלם.
אני מחפש את השם המיוחד לזה שאני לא זוכר בשבוע שעבר.
כן. אלה הם סוגים של מינים שיכולים להשיג דברים הגדולים בO של n.
אבל יש מגבלות, כמו שאתה יכול להשתמש במספרים שלמים רק עד מספר מסוים.
בנוסף, אם אתה מנסה למיין זהו זה משהו -
אם המערך שלך הוא 012, -12, 151, 4 מ'
אז שאלמנט אחד הוא הולך להרוס את כל המיון לחלוטין.
>> שאלות?
[תלמיד] אם יש לך פונקציה רקורסיבית וזה רק גורם קריאות רקורסיביות
בתוך הצהרת תמורה, זה זנב רקורסיבית,
וכך הייתי שלא להשתמש יותר זיכרון במהלך הריצה
או שזה יהיה לפחות להשתמש בזיכרון כלהשוות איטרטיבי פתרון?
[אודן] כן.
סביר להניח שמצב יהיה קצת איטי, אבל לא ממש.
זנב רקורסיבית הוא די טוב.
במבט חוזר במסגרות ערימה, יניח שיש לנו עיקרי
ויש לנו שורת int (int x) או משהו.
זו אינה פונקציה רקורסיבית מושלמת, אבל בר תשואה (x - 1).
אז ברור, זה פגום. אתה צריך מקרי בסיס וכאלה.
אבל הרעיון כאן הוא שמדובר בזנב רקורסיבית,
כלומר, כאשר שורת שיחות עיקרית שזה הולך להגיע מסגרת המחסנית שלו.
במסגרת זו יש ערימה הולכת להיות גוש קטן של זיכרון
מתאים לx הטיעון שלו.
וכך תניחו עיקרי קורה להתקשר בר (100);
אז x הולך מתחיל כ100.
אם המהדר מכיר בכך שזה פונקציה רקורסיבית זנב,
אז כאשר הבר עושה השיחה רקורסיבית לבר,
במקום לגרום מסגרת מחסנית חדשה, שבו הערימה מתחילה גדלה במידה רבה,
סופו של דבר זה יפעל לערימה ואז אתה מקבל segfaults
משום שזיכרון מתחיל התנגשות.
>> אז במקום לגרום מסגרת מחסנית משלו, הוא יכול להבין,
היי, אני לא באמת צריך לחזור למסגרת חבילה זו,
אז במקום זה פשוט יחליף את הטיעון הזה עם 99 ולאחר מכן להתחיל בכל בר.
ואז הוא יעשה את זה שוב והיא תגיע בר תשואה (x - 1),
ובמקום לגרום מסגרת מחסנית חדשה, זה יהיה פשוט להחליף את הטיעון הנוכחי שלה עם 98
ואז לקפוץ חזרה להתחלה של בר.
אלה פעולות, מחליף את ערך 1 בערימה וקפצתי חזרה להתחלה,
הם די יעילים.
אז לא רק הוא זה אותו השימוש בזיכרון כפונקציה נפרדת האיטרטיבי
כי אתה רק באמצעות מסגרת מחסנית 1, אבל אתה לא סובל חסרונות
שיש לקרוא לפונקציות.
פונקציות חיוג יכולות להיות קצת יקרות, כי יש לעשות את כל התקנה זו
ופירוק וכל הדברים האלה.
אז רקורסיה זנב זה טוב.
[תלמיד] מדוע לא ליצור שלבים חדשים?
כי הוא מבין שזה לא צריך.
הקריאה לסרגל פשוט חוזרת שיחה רקורסיבית.
אז הוא לא צריך לעשות שום דבר עם ערך ההחזרה.
זה רק הולך להחזיר אותו באופן מיידי.
אז זה רק הולך להחליף את הטיעון שלו ולהתחיל מחדש.
וגם, אם אין לך את הגרסה רקורסיבית הזנב,
ואז אתה מקבל את כל הברים האלה בי כאשר סרגל זה מחזיר
יש להחזיר את הערך שלו לזה, ואז שהבר מייד חוזר
וזה מחזיר את הערך שלו לזה, אז זה רק הולך לחזור מייד
ולהחזיר את הערך שלו לזה.
אז אתה שומר את זה צץ כל הדברים האלה מעל הערימה
מאז ערך ההחזרה הוא פשוט הולך לעבור את כל הדרך חזרה למעלה בכל מקרה.
אז למה לא פשוט להחליף את הטיעון שלנו בטענה מתעדכנת ולהתחיל מחדש?
אם הפונקציה אינה רקורסיבית זנב, אם אתה עושה משהו כמו -
[תלמיד] אם בר (x + 1). >> כן.
>> אז אם אתה שם אותו במצב, ואז אתה עושה משהו עם ערך ההחזרה.
או אפילו אם אתה פשוט עושה את התשואה 2 * הבר (x - 1).
אז עכשיו בר (x - 1) צריך לחזור על מנת שזה כדי לחשב 2 פעמים שערך,
אז עכשיו זה צריך מסגרת ערימה הנפרדת משלו,
ועכשיו, לא משנה כמה קשה אתה מנסה, אתה הולך צריך -
זה לא זנב רקורסיבית.
[תלמיד] האם אני מנסה להביא רקורסיה לשאוף לרקורסיה זנב -
[אודן] בעולם אידיאלי, אבל בCS50 אתה לא צריך.
על מנת לקבל רקורסיה זנב, בדרך כלל, אתה מגדיר את טיעון נוסף
שם בר ייקח x int לתוך y
ו-y מתאים לדבר האולטימטיבי שאתה רוצה לחזור.
אז זה שאתה הולך תחזור בר (x - 1), 2 * y.
אז זה רק ברמה גבוהה איך להפוך את הדברים להיות זנב רקורסיבית.
אבל טיעון הנוסף -
ואז בסופו של דבר כאשר אתה מגיע למקרה הבסיס שלך, אתה פשוט לחזור y
מכיוון שאספת כל הזמן את ערך ההחזרה רצויה.
אתה סוג של עושה את זה כבר אבל ערוך את השימוש בשיחות רקורסיבית.
שאלות?
[תלמיד] אולי על חשבון מצביע, כמו בעת שימוש בחוטים. >> בטח.
פעולות אריתמטיות על מצביעים.
בעת שימוש במחרוזות זה קל משום שמייתרים הם כוכבי char,
תווים הם לנצח ותמיד בית אחד,
וכן פעולות אריתמטיות על המצביעים הן שווות ערך לחשבון רגיל כאשר אתה מתעסק עם חוטים.
בואו רק נאמר char * s = "שלום".
אז יש לנו בלוק בזיכרון.
זה צריך 6 בתים כי אתה תמיד צריך null terminator.
וchar * s עומדת להצביע על תחילתו של מערך זה.
אז זה מצביע לשם.
עכשיו, זה בעצם איך כל מערך עובד,
ללא קשר לשאלה אם זה היה החזר על ידי malloc או בין אם זה בערימה.
כל מערך הוא בעצם מצביע לתחילת המערך,
ואז כל פעולת מערך, כל אינדקס, הוא פשוט נכנס לאותו מערך מסוים קיזוז.
>> לכן, כאשר אני אומר משהו כמו זה [3]; זה הולך וספירה של 3 תווים פנימה
אז זה [3], יש לנו 0, 1, 2, 3, כך שנ [3] הולך להתייחס לזה אני.
[תלמיד] ואנחנו יכולים להגיע לאותו ערך על ידי ביצוע של + 3 ולאחר מכן כוכב סוגריים?
כן.
זה שווה * (s + 3);
וזה לנצח, ותמיד שווה לא משנה מה אתה עושה.
אתה לא צריך להשתמש בתחביר הסוגר.
אתה תמיד יכול להשתמש ב* (s + 3) תחביר.
אנשים שנוטים לאהוב את תחביר הסוגר.
[תלמיד] אז כל המערכים הם למעשה רק מצביעים.
קיימת הבחנה קלה כשאני אומר int x [4]; >> [תלמיד] שהאם ליצור את הזיכרון?
[אודן] זה הולך ליצור 4 ints במחסנית, ולכן 16 בתים באופן כללי.
זה הולך ליצור 16 בתים על הערימה.
x אינו מאוחסן בכל מקום.
זה רק סמל מתייחס לתחילתו של העניין.
מכיוון שאתה הצהרת המערך פנימי של פונקציה זו,
מה המהדר הוא הולך לעשות הוא פשוט להחליף את כל המופעים של x משתנה
עם בו זה קרה לבחור לשים 16 בתים אלה.
זה לא יכול לעשות את זה עם char * בגלל זה הוא מצביע בפועל.
זה בחינם ואז להצביע על דברים אחרים.
x הוא קבוע. אתה לא יכול לקבל אותו צבע על מערך שונה. >> [תלמיד] אוקיי.
אבל הרעיון הזה, אינדקס זה, הוא זהה, ללא קשר לשאלה אם זה מערך מסורתי
או אם זה מצביע על משהו או אם זה מצביע למערך malloced.
ולמעשה, זה כל כך שווה שזה גם את אותו הדבר.
זה בעצם רק מתרגם מה בתוך הסוגריים ומה שנשארו מאת הסוגריים,
מוסיף אותם יחד, וdereferences.
כך שזה רק תקף כ* (s + 3) או של [3].
[תלמיד] האם יש לך עצות המצביעות על מערכים 2-ממדיים?
>> זה קשה יותר. באופן מסורתי, לא.
מערך 2-ממדי הוא פשוט מערך 1-ממדי עם חלק תחביר נוח
כי כשאני אומר int x [3] [3], זה באמת רק מערך 1 עם 9 ערכים.
ולכן, כשהמדד, המהדר יודע למה אני מתכוון.
אם אני אומר x [1] [2], הוא יודע שאני רוצה ללכת לשורה השנייה, אז זה הולך לדלג על 3 הראשונים,
ואז הוא רוצה את הדבר השני בזה, אז זה הולך לקבל את זה.
אבל זה עדיין רק במערך בודד ממדי.
ואז אם אני רוצה להקצות למצביע שהמערך,
הייתי אומר int * p = x;
הסוג של x הוא פשוט -
זה סוג אמירה גס של x שכן הוא רק סמל וזה לא משתנה בפועל,
אבל זה רק * int.
x הוא פשוט מצביע להתחלה של זה. >> [תלמיד] אוקיי.
ואז אני לא יוכל לגשת [1] [2].
אני חושב שיש תחביר מיוחד להכרזה על מצביע,
משהו מגוחך כמו int (* p [-. משהו מגוחך לחלוטין אני אפילו לא יודע.
אבל יש תחביר להכרזת מצביעים כמו עם סוגריים וכל מיני דברים.
זה אפילו לא יכול לתת לך לעשות את זה.
אני יכול להביט אחורה על משהו שהיה אומר לי את האמת.
אני אסתכל על זה מאוחר יותר, אם יש תחביר לנקודה. אבל אתה אף פעם לא תראה אותו.
ואפילו התחביר הוא כל כך ארכאי, שאם אתה משתמש בו, אנשים יהיו מבולבלים.
מערכים רבים ממדים הם די נדירים כפי שהוא.
אתה פחות או יותר -
ובכן, אם אתה עושה דברי מטריצה ​​זה לא הולך להיות נדיר,
אבל ב-C אתה רק לעתים נדירות הולך להיות באמצעות מערכים רבים ממדים.
כן. >> [תלמיד] בואו נגיד שיש לך באמת זמן מערך.
>> אז בזיכרון וירטואלי זה להיראות כל ברציפות,
כמו האלמנטים זכות זו לצד זו,
אבל בזיכרון הפיזי, זה יהיה אפשרי עבור שיש לפצל את? >> כן.
איך עובד זיכרון וירטואלי שזה רק מפריד -
יחידת ההקצאה היא דף, שנוטה להיות 4 ק"ג,
ולכן כאשר תהליך אומר, היי, אני רוצה להשתמש בזיכרון הזה,
מערכת ההפעלה הולכת להקצות אותו 4 ק"ג שלגוש הקטן של זיכרון.
גם אם אתה משתמש בייט אחד קטן בכל הבלוק של זיכרון, רק
מערכת ההפעלה הוא הולך לתת לו את 4 ק"ג המלא.
אז מה שזה אומר זה שיהיה לי - בואו נגיד שזה הערימה שלי.
מערך זה יכול להיות מופרד. הערימה שלי יכלה להיות מגה ומגה.
הערימה שלי יכולה להיות ענקית.
אבל הערימה עצמו יש להיות מפוצל לדפים בודדים,
שאם אנחנו מסתכלים על כאן בואו נגיד שזה הזיכרון RAM שלנו,
אם יש לי 2 ג'יגה בייט של זכרון RAM, זה הוא 0 כתובת ממשית כמו בייט של זכרון RAM 0,
וזה כאן 2 ג'יגה כל הדרך למטה.
אז דף זה עשוי להתאים לבלוק הזה כאן.
ייתכן שדף זה מתאים לבלוק הזה כאן.
זה עשוי להתאים לזה כאן.
אז מערכת ההפעלה היא חופשיה להקצות זיכרון פיזי
לכל דף באתר באופן שרירותי.
וזה אומר שאם גבול זה קורה כדי לרכב על מערך,
מערך במקרה נותר מכל זה וזכותו של צו זה של דף,
אז מערך שהוא הולך להיות מפוצל בזיכרון פיזי.
ואז כשאתה יוצא מהתכנית, כאשר התהליך מסתיים,
מיפויים אלו מקבלים נמחקו ואז זה חופשי לשימוש בלוקים קטנים האלה לדברים אחרים.
עוד שאלות?
[התלמיד] פעולות אריתמטיות על המצביעים. >> אה, כן.
מייתרים היו יותר קלים, אבל מסתכלים על משהו כמו ints,
כך חזרה לint x [4];
בין אם זה מערך או בין אם זה מצביע למערך malloced של 4 מספרים שלמים,
זה הולך להיות מטופלים באותה הדרך.
[תלמיד] אז מערכים הם בערימה?
[אודן] מערכים לא נמצאים בערימה. >> [תלמיד] הו.
>> [אודן] סוג זה של מערך נוטה להיות במחסנית
אלא אם הכרזת עליו ב-- התעלמות משתנים גלובליים. אין להשתמש במשתנים גלובליים.
בתוך פונקציה אני אומר int x [4];
זה הולך ליצור בלוק 4-מספר שלם במחסנית למערך זה.
אבל זה malloc (4 * sizeof (int)); הולך ללכת על הערימה.
אבל אחרי נקודה זו אני יכול להשתמש x ו-p בפחות או יותר את אותן דרכים,
למעט החריגים שאמרתי קודם עליך יכול להקצות מחדש p.
מבחינה טכנית, הגדלים שלהם שונים במקצת, אבל זה לחלוטין לא רלוונטי.
לעולם לא תשתמש בם הגדלים.
P אני יכול לומר p [3] = 2; או x [3] = 2;
אתה יכול להשתמש בם בדיוק באותן הדרכים.
אז פעולות אריתמטיות על מצביעים עכשיו - כן.
[תלמיד] אתה לא צריך לעשות * p אם יש לך את הסוגריים?
בסוגריים הם dereference סמוי. >> אוקיי.
למעשה, גם מה שאתה אומר עם אתה יכול לקבל מערכים רבים ממדים
עם מצביעים, מה שאתה יכול לעשות הוא משהו כמו, יניח, int ** PP = malloc (sizeof (int *) * 5);
אני רק כותב את כל זה בתחילת המופע.
אני לא רוצה את זה.
אוקיי.
מה שעשיתי כאן הוא - זה צריך להיות עמ '[i].
אז PP הוא מצביע למצביע.
אתה mallocing עמ להצביע למערך של 5 כוכבי int.
אז בזיכרון יש לך בעמ 'הערימה
זה הולך להצביע למערך של 5 בלוקים שהם עצמם כל מצביעים.
ואז כשmalloc כאן למטה, אני malloc שכל אחד מהמצביעים הבודדים האלה
צריך להצביע על בלוק נפרד של 4 בתים בערמה.
אז זה מצביע על 4 בתים.
ואחת נקודות זה ל4 בתים שונים.
>> וכולם מצביעים על 4 הבתים שלהם.
זה נותן לי דרך לעשות דברים רבים ממדיים.
אני יכול לומר pp [3] [4], אבל עכשיו זה לא אותו הדבר כמו מערכים רב ממדים
בגלל מערכים רבים ממדיו תורגמו [3] [4] ליחיד לקזז לתוך מערך x.
עמ dereferences זה, ניגש המדד השלישי, אז שdereferences
וכניסות - 4 תהיינה חוקיות - המדד השני.
לעומת זאת, כאשר היו לנו int x [3] [4] לפני כמערך רב ממדי
וכאשר אתה לוחץ פעמי סוגר זה באמת רק dereference יחיד,
אתה עוקב אחרי מצביע בודד ולאחר מכן לקזז,
זה באמת הפניות 2D.
אתה עוקב 2 מצביעים נפרדים.
אז זה גם מבחינה טכנית מאפשר לך יש מערכים רבים ממדים
שבו כל פרט הוא מערך גדלים שונים.
אז אני חושב שמערכים רבים ממדים משוננים הוא מה זה נקרא
מאז באמת הדבר הראשון שיכל להצביע על משהו שיש לו 10 אלמנטים,
הדבר השני יכל להצביע על משהו שיש לו 100 אלמנטים.
[תלמיד] האם יש הגבלה למספר המצביעים שאתה יכול לקבל
הצבעה על מצביעים אחרים? מספר >>
אתה יכול להיות int ***** p.
חזרו לפעולות אריתמטיות על מצביעים - >> [תלמיד] הו. >> כן.
[תלמיד] אם יש לי int *** p ואז אני עושה ביטול הפניה ואני אומר * p שווה לערך הזה,
האם זה רק הולך לעשות רמת 1 של ביטול הפניה? >> כן.
אז אם אני רוצה לגשת הדבר שהמצביע האחרון מצביע על -
אז אתה עושה עמ '***. >> אוקיי.
אז זה p מצביע לבלוק 1, מצביע על בלוק נוסף, מצביע על בלוק אחר.
אז אם אתה עושה * p = משהו אחר, אז אתה משנה את זה
עכשיו להצביע לגוש אחר. >> אוקיי.
>> [אודן] ואם אלה malloced, אז יש לך עכשיו דלף זיכרון
אלא אם קורה לך יש הפניות שונות של אלה
מאחר שאתה לא יכול לחזור לאלה אלה שפשוט זרקו.
פעולות אריתמטיות על מצביעים.
הולך להקצות מערך של 4 מספרים שלמים, x [4] int
כאשר x הולך להצביע על תחילתו של המערך.
לכן, כאשר אני אומר משהו כמו x [1]; אני רוצה שזה אומר ללכת למספר השלם השני במערך,
שיהיה זה אחד.
אבל באמת, זה 4 בתים לתוך המערך השלם מאז זה לוקח עד 4 בתים.
אז קיזוז של 1 באמת אומר קיזוז של 1
פעמים את הגודל של כל הסוג של המערך הוא.
זהו מערך של מספרים שלמים, ולכן הוא יודע לעשות 1 פעמי גודל של int מתי היא רוצה לקזז.
תחביר האחר. זכור שזה שווה ערך ל * (x + 1);
כשאני אומר מצביע + 1, מה שמחזיר הוא הכתובת שמצביע האחסון
בתוספת 1 פעמים בגודל של הסוג של המצביע.
אז אם x = ox100, אז x + 1 = ox104.
ואתה יכול להשמיץ את זה ואומר משהו כמו char * c = (char *) x;
ועכשיו ג הולך להיות באותה הכתובת כמו x.
ג הולך להיות שווה לox100,
אבל c + 1 הולך להיות שווה לox101
מאז פעולות אריתמטיות על מצביעים תלוי בסוג של המצביע שאתה מוסיף ל.
אז c + 1, זה נראה בג, זה מצביע char, אז זה הולך להוסיף 1 פעמי גודל של char,
שתמיד הולך להיות 1, כך שאתה מקבל 101,
ואילו אם אני עושה x, שגם הוא עדיין 100, x + 1 הולך להיות 104.
[תלמיד] אתה יכול להשתמש C + + כדי לקדם את המצביע עד ליום 1?
כן, אתה יכול.
אתה לא יכול לעשות את זה עם x כי x הוא רק סמל, הוא קבוע, אתה לא יכול לשנות את x.
>> אבל ג קורה רק כדי להיות מצביע, אז + + C הוא חוקי לחלוטין וזה יהיה להגדיל ב 1.
אם ג היה פשוט * int, אז C + + יהיה 104.
+ + פעולות אריתמטיות על מצביעים עושה בדיוק כמו c + 1 יצטרך לעשות פעולות אריתמטיות על מצביעים.
זו בעצם איך המון דברים כמו סוג המיזוג -
במקום ליצור העתקים של דברים, אתה יכול במקום לעבור -
כאילו שאם אני רוצה לעבור את המחצית זו של המערך - בוא נמחק חלק מזה.
נניח שאני רוצה לעבור את הצד הזה של המערך לפונקציה.
מה הייתי עובר לפונקציה?
אם אני עובר x, אני מעביר את הכתובת הזאת.
אבל אני רוצה להעביר את הכתובת המסוימת הזה. אז מה כדאי לי לעבור?
[תלמיד] פוינטר + 2?
[אודן] אז x + 2. כן.
זה הולך להיות זו כתובת.
גם אתה לעתים קרובות מאוד לראות את זה כx [2] ולאחר מכן את הכתובת שלו.
אז אתה צריך לקחת את הכתובת שלו כי הסוגר הוא dereference סמוי.
x [2] מתייחס לערך שהוא בתיבה זו, ואז אתה רוצה את הכתובת של תיבה,
אז אתה אומר & x [2].
אז זה משהו איך בסוג המיזוג שבו אתה רוצה להעביר חצי מהרשימה למשהו
אתה באמת רק לעבור & x [2], ועכשיו ככל השיחה רקורסיבית היא מודאגת,
המערך החדש שלי מתחיל שם.
שאלות הרגע אחרונות.
[תלמיד] אם לא מכניס אמפרסנד או - מה שנקרא ש? >> כוכב?
[תלמיד] כוכב. >> בחינה טכנית, מפעיל dereference, אבל - >> [תלמיד] Dereference.
>> אם אנחנו לא לשים כוכב או אמפרסנד, מה קורה אם אני פשוט אומר y = x ו-x הוא מצביע?
מה הוא הסוג של Y? >> [תלמיד] אני פשוט אגיד את המצביע זה 2.
אז אם אתה רק אומר y = x, עכשיו x ו-y נקודה לאותו הדבר. >> [תלמיד] צבע על אותו הדבר.
ואם x הוא מצביע int? >> זה הייתי מתלונן, כי אתה לא יכול להקצות מצביעים.
[תלמיד] אוקיי.
זכור כי מצביעים, למרות שאנחנו מציירים אותם כחצים,
באמת כל מה שהם החנות - int * x - באמת כל x הוא האחסון היא משהו כמו ox100,
שמקרה אנו מייצגים כמצביע לבלוק מאוחסן ב 100.
לכן, כאשר אני אומר int * y = x; אני פשוט מעתיק ox100 לתוך y,
שאנחנו רק הולכים לייצג כy, גם הוא מכוונים לox100.
ואם אני אומר אני int = (int) x, ואז אני הולך לאחסון כל מה שהערך של ox100 הוא
בתוכו, אבל עכשיו זה הולך להתפרש כשלם במקום מצביע.
אבל אתה צריך גבס או אחר זה לא יתלונן.
[תלמיד] אז אתה מתכוון להטיל -
האם זה הולך להיות ליהוק int של int x או ליהוק של Y?
[אודן] מה?
[תלמיד] אוקיי. לאחר סוגריים אלה הולך להיות x או איים שם?
>> [אודן] בכל מקרה. x ו-y הם שווים ערך. >> [תלמיד] אוקיי.
בגלל שהם שני המצביעים. >> כן.
[תלמיד] אז זה היית לאחסן 100 הקסדצימלי בצורה שלמה? >> [אודן] כן.
אבל לא את הערך של מה שהוא מצביע.
[אודן] כן. >> [תלמיד] אז רק את הכתובת בצורה שלמה. אוקיי.
[אודן] אם אתה רוצה מסיבה מוזרה,
אתה רק יכול להתמודד עם מצביעים ולא להתמודד עם מספרים שלמים
ופשוט להיות כמו int * x = 0.
ואז אתה הולך באמת מתבלבל פעם אחת פעולות אריתמטיות על מצביעים מתחילות לקרות.
אז המספרים שהם בחנות הם חסרי משמעות.
זה רק איך אתה בסופו מפרש אותם.
אז אני חופשי להעתיק ox100 מ* int לint,
ואני חופשי להקצות - אתה כנראה הולך צועק עליי על שלא ליהוק -
אני חופשי להקצות משהו כמו (int *) ox1234 ל* int שרירותי זה.
אז ox123 הוא בדיוק כמו תוקף כתובת זיכרון כפי שהיא & y.
& Y קורה להחזיר משהו שהוא פחות או יותר ox123.
[תלמיד] שזה יהיה ממש מגניבת דרך ללכת מהקסדצימלי לצורה עשרונית,
כמו שאם יש לך מצביע ואתה מטיל אותה כint?
[אודן] אתה באמת יכול פשוט להדפיס באמצעות כמו printf.
בואו נגיד שיש לי int y = 100.
(אז printf% d \ n - כפי שאתם כבר אמורים לדעת - להדפיס שכמספר שלם,% x.
אנחנו פשוט להדפיס אותו כהקסדצימלי.
אז מצביע אינו מאוחסן כההקסדצימלי,
ושלם אינו מאוחסן כעשרוני.
הכל מאוחסן כמו בינארי.
זה רק שאנו נוטים להראות כמצביעים הקסדצימלי
כי אנחנו חושבים על דברים בלוקים 4-byte אלה,
וכתובות זיכרון נוטות להיות מוכר.
אנחנו כמו, אם זה מתחיל עם BF, אז זה קורה להיות בערימה.
אז זה רק הפרשנות שלנו של מצביעים כהקסדצימלי.
אוקיי. כל השאלות אחרונות?
>> אני אהיה כאן קצת אחרי אם יש לך משהו אחר.
וזה סוף הסיפור.
>> [תלמיד] יש! [מחיאות כפות]
>> [CS50.TV]