C/C++ מדריכי
הצגה מזורזת :C++ הבנת
C++ הקדמה להיררכית מחלקות ב C הקדמה לתכנות ב דף הבית

שיעור 1 - הקדמה

לאנשים רבים המעבר מ C ל ++C אינו קל. לאמיתו של דבר, מעבר זה מלווה לעיתים קרובות בהרבה חרדה כיוון ש ++C מוקפת בהילה מסוימת שעושה אותה ללא נגישה. לדוגמא, ניתן לקחת ספר על ++C, לפתוח בעמוד מסוים בצורה אקראית, ולהיתקל בפסקה כגון זו:

"מפרספקטיבה של תוכנית, הפקה פרטית הנה שקולה לבלימה פרט לסוגייה (החשובה לפרקים) של הפעלה ידנית. שימוש חשוב של זה הוא הטכניקה של הפקת מחלקה פומבית ממחלקת בסיס אבסטרקטית תוך הגדרת ממשק ופרטיות ממחלקה ממשית שמספקת יישום. כיוון שהירושה שעולה מהפקה פרטית היא יחידת ביצוע שאינה משתקפת בטיפוס של המחלקה המופקת, היא לפעמים מכונה 'ירושת יישום' ומושוות עם הצהרה פומבית, בה הממשק של מחלקת הבסיס עבר בירושה וההמרה המוחלטת לטיפוס הבסיס מותרת. השני לעיתים מאוזכר כ sub-typing או 'ירושת ממשק'." [מתוך "שפת התכנות ++C, מהדורה שניה", מאת Bjarne Stroustrup, עמוד 413]

זה יכול להיות קשה להתחיל בסביבה כה טיפשית.

המטרה של שיעורים אלו היא לעזור לך להשיג הבנה של המושגים היסודיים שמובילים את ++C במהירות וללא ייסורים. הם מאפשרים לך להתחיל לחשוב "בצורה מונחית עצמים". ברגע שתבין את היסודות, יתר השפה יהיה ברור באופן יחסי כי יהיה לך שלד לחבר עליו פרטים אחרים כשתזדקק להם. ברגע שתבין את הנושאים היסודיים ואת אוצר המילים, ++C מתגלה כשפה מצוינת עם כוח משמעותי למדי. כשמשתמשים בה נכון, היא יכולה לשפר באופן דרמטי את היצרניות שלך כמתכנת.

מדריכים אלו עונים על שלוש שאלות נפוצות:

1. מדוע ++C קיימת, ומהם היתרונות שלה על C?

2. אילו כלים זמינים ב ++C לבטא רעיונות מונחי עצמים?

3. איך מתכננים ומבצעים קוד תוך שימוש בעקרונות מונחי עצמים?

ברגע שאתה מבין את הכלים הבסיסיים הזמינים ב ++C ויודע איך ולמה להשתמש בהם, אתה הופך למתכנת ++C. שיעורים אלו יזניקו אותך בדרך זו, ויהפכו חומר אחר ב ++C (אפילו Stroustrup) להרבה יותר קל להבנה.

שיעורים אלו מניחים שאתה כבר מכיר את C. אם זה לא המקרה, בלה שבוע או שבועיים בסיגול עצמך לשפת C ולאחר מכן חזור לשיעורים אלו. ++C היא הרחבה של C, כך שכמעט כל דבר שאתה יודע על C ימופה ישירות לשפה חדשה זו.

1.1 מדוע ++C קיימת?

לאנשים ש ++C חדשה להם, או שמנסים ללמוד עליה מספרים, יש לעיתים קרובות שתי שאלות עיקריות: 1) "לכל מה שקראתי תמיד היה אוצר מילים מטורף -- 'סגירה בבועה', 'ירושה', 'פונקציות וירטואליות', 'מחלקות', 'הגדרה מחדש', 'חברות' -- מאיפה כל הדברים האלו באים?" ו 2) "השפה הזו -- ותכנות מונחה עצמים באופן כללי - ללא ספק כוללים שינוי מנטלי גדול, לכן איך אני לומד לחשוב בשיטה של ++C?" על שתי שאלות אלו ניתן לענות, והתכנון של ++C באופן כללי הוא הרבה יותר קל לעיכול, אם אתה יודע מה המתכננים של ++C ניסו להשיג כאשר הם יצרו את השפה. אם אתה מבין מדוע המתכננים עשו את הבחירות שעשו, ולמה הם תכננו תכונות מסוימות בתוך השפה, הרבה יותר קל להבין את השפה עצמה.

תכנון שפה הוא תהליך מתפתח. שפה חדשה לעיתים קרובות נוצרת על ידי למידת לקח משפות קודמות, או באמצעות ניסיון להוסיף רעיונות שנהגו לאחרונה לשפה. שפות גם מתפתחות כדי לפתור בעיה מסוימת. לדוגמא, שפת Ada פותחה בראש ובראשונה כדי לפתור בעיה מרגיזה שעמדה בפני הפנטגון. מתכנתים שכתבו קוד למערכות צבאיות שונות השתמשו במאות שפות שונות, וזה היה בלתי אפשרי לתחזק או לעדכן אחר כך את המערכות בגלל זה. Ada מנסה לפתור חלק מבעיות אלו על ידי צירוף התכונות הטובות של הרבה שפות שונות לשפה בודדת.

דוגמא טובה נוספת לתהליך ההתפתחות בשפות מחשב אירעה בפיתוח של שפות מובנות. שפות אלו הופיעו בתגובה לבעיה גדולה שלא נצפתה על ידי מתכנני שפות מוקדמות: שימוש היתר בפקודת ה goto בתכניות גדולות. בתוכנית קטנה, פקודות goto לא מהוות בעיה. אבל בתוכנית גדולה, בייחוד כאשר משתמש בה אדם שאוהב פקודות goto, הן נוראיות. הן הופכות את הקוד ללא מובן לחלוטין לכל מי שמנסה לקרוא אותו בפעם הראשונה. שפות התפתחו כדי לפתור את הבעיה הזו, תוך הוצאת פקודות ה goto לחלוטין והבאה לידי כך שקל יותר לפרק תכניות גדולות לפונקציות ומודולים שניתן להשתלט עליהם.

++C היא שפה "מונחית עצמים". תכנות מונחה עצמים הוא תגובה לבעיות תכנות שנראו לראשונה בתכניות גדולות שפותחו בשנות ה 70. כל השפות מונחות העצמים מנסות להשיג שלושה דברים כדרך לסיכול הבעיות שטבועות בפרויקטים גדולים:

1. כל השפות מונחות העצמים מיישמות "הפשטת נתונים" בצורה נקיה תוך שימוש ברעיון שנקרא "מחלקות". אנחנו נסתכל על הפשטת נתונים יותר לעומק בהמשך כיוון שזה רעיון מרכזי ב ++C. בקצרה, הפשטת נתונים הנה דרך לצירוף נתונים עם הפונקציות שמשמשות לתפעל נתונים אלו כך שפרטי היישום מוסתרים מהמתכנת. הפשטת נתונים הופכת תכניות להרבה יותר קלות לתחזוק ועדכון.

2. כל השפות מונחות העצמים מנסות להפוך חלקים של תכניות לקלים לשימוש חוזר וברי הרחבה. מכאן באה המילה "אובייקט". תכניות נשברות לאובייקטים ניתנים לשימוש חוזר. אובייקטים אלו יכולים אז להתקבץ יחד בצורות שונות כדי ליצור תכניות חדשות. ניתן גם להרחיב אובייקטים קיימים. על ידי כך שנותנים למתכנתים דרך נקיה מאוד לעשות שימוש חוזר בקוד, ועל ידי כך שמכריחים מתכנתים לכתוב קוד בדרך זו, הרבה יותר קל לכתוב תכניות חדשות על ידי הרכבת חלקים קיימים.

3. שפות מונחות עצמים מנסות להפוך קוד קיים לניתן לשינוי בקלות בלי לשנות למעשה את הקוד. זוהי תפיסה יחידה במינה ורבת עצמה, כיוון שבתחילה זה לא נראה אפשרי לשנות משהו מבלי לשנות אותו. על ידי שימוש בשני הרעיונות בכל אופן -- ירושה ורב צורתיות -- זה אפשרי לעשות זאת. האובייקט הקיים נשאר זהה, והשינויים נערכים עליו בשכבות. יכולת המתכנת לתחזק ולהתאים קוד ללא באגים משתפרת באופן דרסטי עם השימוש בגישה זו.

כיוון ש ++C היא שפה מונחית עצמים, היא מחזיקה בשלושת היתרונות של שפות מונחות עצמים שנידונו לעיל. ++C מוסיפה שני שיפורים אחרים משל עצמה כדי לנקות בעיות בשפת C המקורית או כדי להפוך את התכנות ב ++C לקל יותר מאשר ב C:

1. ++C מוסיפה מושג שנקרא "הגדרה מחדש של אופרטורים". תכונה זו מאפשרת לך לתאר דרכים חדשות להשתמש באופרטורים כמו "+" ו "<<" בתוכנית שלך. לדוגמא, אם אתה רוצה להוסיף טיפוס חדש כגון טיפוס של מספרים מרוכבים לתוכנית C, הביצוע לא יהיה נקי. כדי לחבר שני מספרים מרוכבים, תצטרך ליצור פונקציה בשם "add" ואז לכתוב "c3=add(c1,c2);", כאשר c2 ,c1, ו c3 הם ערכים מטיפוס המספרים המרוכבים החדש. ב ++C, ניתן במקום זאת להגדיר מחדש את האופרטורים "+" ו "=", כך שאתה יכול לומר "c3=c1+c2". בצורה זו, טיפוסים חדשים מתווספים לשפה ללא תפרים לחלוטין. הרעיון של הגדרה מחדש משתרע על כל הפונקציות שנוצרות ב ++C.

2. ++C גם מנקה את הביצוע של מספר חלקים בשפת C, החשובים בהם קלט/פלט והקצאת זיכרון. היישומים החדשים נוצרו עם מבט להגדרה מחדש של אופרטורים, כך שקל להוסיף טיפוסים חדשים ולספק פעולות קלט/פלט והקצאת זיכרון בשבילם ללא תפרים.

בואו נבחן מספר דוגמאות של בעיות בהן בוודאי נתקלת בניסיונותיך בתכנות ב C, ואז נבחן את הצורה שבה הן נפתרות ב ++C.

את הדוגמא הראשונה ניתן לראות בכל ספריה שנבנית ב C. הבעיה מודגמת בקוד להלן, שמכניס למחרוזת ערך מסוים ואז משרשר לה מחרוזת אחרת:

char s[100];
strcpy(s, "hello ");
strcat(s, "world");

קוד זה אינו יפה כל כך, אבל המבנה אופייני לכל ספריה שתיצור ב C. טיפוס המחרוזת בנוי מטיפוס מערך התווים שבנוי ב C. כיוון שהטיפוס החדש איננו חלק מהשפה המקורית, המתכנת נאלץ להשתמש בקריאות לפונקציה כדי לעשות אתו כל דבר. מה שהיית רוצה לעשות במקום זאת זה שתהיה לך אפשרות ליצור טיפוס חדש שניתן יהיה למזג אותו ללא תפרים עם יתר השפה. משהו כגון זה:

string s;
s = "hello ";
s += "world";

אם זה היה אפשרי, השפה היתה ברת הרחבה לאין שיעור. ++C תומכת בהרחבה שכזו באמצעות הגדרה מחדש של אופרטורים ומחלקות. שים לב גם שבאמצעות השימוש בטיפוס string, היישום סמוי לחלוטין. כלומר, אתה לא יודע ש -- או אם -- string נוצר תוך שימוש במערך של תווים, רשימה מקושרת, וכו' ונראה שאין לו אורך מקסימלי. לכן קל יותר לשנות את היישום של הטיפוס בעתיד מבלי להשפיע לרעה על קוד קיים. דוגמא נוספת שמשתמשת בספרייה ניתן לראות ביישום של ספריית מחסנית פשוטה. אב הטיפוס של הפונקציה לספריית מחסנית אופיינית (נמצא בדרך כלל בקובץ הכותרת) מוצג להלן:

void stack_init(stack s, int max_size);
int stack_push(stack s, int value);
int stack_pop(stack s, int *value);
void stack_clear(stack s);
void stack_destroy(stack s);

המשתמש בספרייה זו יכול לדחוף למחסנית, להוציא מהמחסנית ולנקות את המחסנית, אבל לפני שפעולות אלו יהיו ברות תוקף יש לאתחל את המחסנית באמצעות stack_init. לאחר שמסיימים עם המחסנית, יש להרוס את המחסנית באמצעות stack_destroy. אבל מה קורה אם שוכחים את צעדי האתחול או ההריסה? במקרה הראשון, הקוד לא יעבוד ויכול להיות קשה מאוד לאתר את הבעיה אלא אם כן כל השגרות בספרייה מגלות כשל באתחול ומדווחות עליו. במקרה השני, הכשל בהריסה של המחסנית כראוי יכול לגרום לדליפות זיכרון ששוב קשה מאוד לאתרן. ++C פותרת בעיה זו על ידי השימוש בבנאים (constructors) והורסים (destructors), שמטפלים באתחול ובהריסה של אובייקטים כמו מחסנית בצורה אוטומטית.

נמשיך עם הדוגמא של המחסנית, שים לב שהמחסנית כפי שהוגדרה יכולה להכניס ולהוציא מספרים שלמים. מה אם אתה רוצה ליצור מחסנית אחרת שיכולה לטפל במספרים ממשיים, ואחרת בשביל תווים? יהיה עליך ליצור שלוש ספריות נפרדות, או לחלופין להשתמש באיחוד ולתת לאיחוד לטפל בכל הטיפוסים האפשריים. ב ++C, מושג שנקרא templates מאפשר לך ליצור רק ספריית מחסנית אחת ולהגדיר מחדש את הטיפוסים שמאוחסנים במחסנית כאשר היא מוצהרת.

בעיה נוספת שעשויה להיות לך כמתכנת C עוסקת בהחלפת ספריות. נניח, לדוגמא, שאתה משתמש בפונקציה printf המוגדרת בספרייה stdio אבל אתה רוצה לשנות אותה כך שהיא תוכל לטפל בטיפוס חדש שהגדרת לאחרונה. לדוגמא, ייתכן שתרצה לשנות את printf כך שהיא תוכל להדפיס מספרים מרוכבים. אתה חסר מזל אלא אם כן יש לך במקרה את קוד המקור של printf. ואפילו אם יש לך את המקור, שינוי לא יועיל כל כך כיוון שהקוד אינו נייד, וגם אין לך את הזכות להעתיק אותו. אין באמת אף דרך להרחיב ספריית C בקלות ברגע שהיא עברה הידור. כדי לפתור את בעיית הפלט שלך, תאלץ ליצור פונקציה חדשה כדי להדפיס את הטיפוס שלך. אם יש לך יותר מטיפוס חדש אחד, קרוב לוודאי שתצטרך ליצור מספר פונקציות פלט שונות, וכולן יהיו שונות. ++C מתמודדת עם כל הבעיות האלו עם הטכניקה החדשה שלה ל standard output. שילוב של הגדרה מחדש של אופרטורים ומחלקות מאפשר לטיפוסים חדשים למזג עצמם בשיטת הקלט/פלט הסטנדרטית של ++C.

כאשר אתה חושב על הפקודה printf, חשוב על התכנון שלה ושאל את עצמך: האם זו דרך טובה לתכנן קוד? בתוך הפקודה printf יש פקודת switch או שרשרת של if-else-if שמנתחת את מחרוזת הפורמט. %d משמש למספרים עשרוניים, %c משמש לתווים, %a משמש למחרוזות, וכן הלאה. יש לפחות שלוש בעיות ביישום זה:

1. על המתכנת לתחזק את פקודת ה switch הזו ולשנות אותה לכל סוג חדש בו יש לטפל. שינוי פירושו שבאגים חדשים עשויים להיווצר.

2. אין עירבון לכך שהמשתמש יתאים את פרמטרי הנתונים עם מחרוזת הפורמט, כך שהמערכת כולה יכולה להיכשל בצורה קטסטרופלית.

3. זה לא בר הרחבה -- אלא אם כן יש לך את הקוד אתה לא יכול להרחיב את פקודת ה printf.

++C פותרת את הבעיות האלו לחלוטין על ידי אילוץ המתכנת לבנות את הקוד בצורה חדשה. פקודת ה switch סמויה ומטופלת בצורה אוטומטית על ידי המהדר באמצעות הגדרה מחדש של אופרטורים. לא ניתן להתאים בצורה לא נכונה את הפרמטרים, ראשית בגלל שב ++C הם לא מיושמים כמו פרמטרים, ושנית כיוון שהטיפוס של המשתנה מפקח בצורה אוטומטית על מנגנון ההחלפה שמיושם על ידי המהדר.

++C פותרת הרבה בעיות נוספות גם כן. לדוגמא, היא פותרת את בעיית "הקוד המצוי שמועתק בהרבה מקומות" על ידי כך שהיא מאפשרת לך לפרק לגורמים קוד מצוי במימד שלישי. היא פותרת את בעיית ה"אני רוצה לשנות את טיפוס הפרמטר שמועבר לפונקציה מבלי לשנות את הפונקציה" עלי ידי כך שהיא מאפשרת לך להגדיר מחדש את אותו שם פונקציה עם רשימות פרמטרים רבות. היא פותרת את בעיית ה"אני רוצה לעשות שינוי קטנטן בדרך שבה זה עובד, אבל אין לי את קוד המקור של זה", ויחד עם זה פותרת את בעיית ה"אני רוצה לכתוב מחדש את הפונקציה הזו לחלוטין אבל בלי לשנות את יתר הספרייה" על ידי שימוש בירושה. היא הופכת את יצירת הספריות להרבה יותר נקיה. היא משפרת בצורה דרסטית את היות הקוד ניתן לתחזוק. וכן הלאה.

עליך לשנות מעט את דרך החשיבה שלך כדי לנצל הרבה מהכוח הזה, ויוצא שבדרך כלל עליך להתייחס קצת יותר לתכנון של הקוד שלך בהתחלה. אם אתה לא עושה זאת, אתה מאבד הרבה מהיתרונות. כפי שאתה רואה, בכל אופן, אתה משיג הרבה בתמורה להשקעה שלך. כמו בכל דבר אחר, יש עסקת חליפין, אבל בדרך כלל היתרונות עולים על החסרונות.




לדף הראשון

לדף הבא >>