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

שיעור 2 - שיפור C

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

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

2.1 הערות

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

// get_it function reads in input values
void get_it()
{

    // do something.

}

המהדר מתעלם מכל מה שנמצא החל מה"//" עד לסוף השורה. ניתן להשתמש בשני סגנוני ההערות לחלופין בתוכנית ++C.

2.2 הסבת טיפוסים

ב C, ניתן להסב טיפוס על ידי שימת שם טיפוס בסוגריים לפני שם המשתנה, כפי שמוצג להלן:

int i;
float f;
f = (float) i;

++C תומכת במבנה נוסף. היא הופכת את ההמרה לנראית כמו קריאה לפונקציה כפי שמוצג כאן:

int i;
float f;
f = float(i);

נראה בהמשך, כאשר נתחיל לדבר על מחלקות, שיש סיבה למבנה חדש זה.

2.3 קלט ופלט

2.3.1 קלט/פלט של מסוף

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

השימוש בספריית ה iostream לקלט ופלט בסיסיים הוא ברור. שתי דוגמאות פשוטות מוצגות להלן:

cout << "hello\n";

או באופן שקול:

cout << "hello" << endl;

שתי הצורות מייצרות את אותו פלט, וגורמות למילה "hello" שאחריה שורה חדשה להופיע על הפלט הסטנדרטי. המילה cout מעידה על stdout כיעד לפלט, והאופרטור >> (אופרטור ההכנסה) משמש לאיסוף הפריטים. שני יעדי פלט סטנדרטי אחרים מוגדרים מראש: cerr לנתוני שגיאה שלא נכלאים בזיכרון, ו clog לנתוני שגיאה שנכלאים בזיכרון.

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

int i = 2;
float f = 3.14;
char c = 'A';
char *s = "hello";
cout << s << c << f << i <<  endl;

מייצר את הפלט:

helloA3.142

והוא זהה ל:

cout << s << c;
cout << f;
cout i << endl;

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

cout << &i << endl;

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

cout << (void *) s;

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

cout << long (& i);

שורה זו מדפיסה את הכתובת של i במבנה עשרוני. באותה דרך, משתמשים בהסבה למספר שלם כדי להדפיס את ערך ה integer של תו:

cout << int('A');	// מייצר את 65 כפלט

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

cout << (2 << 4);		// מייצר את 32 כפלט

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

int i = 2;
float f = 3.14;
char c = 'A';
char *s = "hello";
cout << s << " " << c << "\t" << f
    << "\t" << i << endl;

ישנם מספר מניפולטורים נוספים שניתן להכניס לזרימת פלט (במערכות רבות תאלץ להכליל את "iomanip.h" כדי להשתמש בהם)

השתמש בבסיס עשרוני

dec

השתמש בבסיס אוקטלי

oct

השתמש בבסיס הקסדצימלי

hex

סוף שורה

endl

סוף מחרוזת ('0\')

ends

רוקן את מכלא הפלט

flush

קבע את רוחב הפלט ל w (ברירת המחדל היא 0)

setw(w)

קבע את תו המילוי ל c (ברירת המחדל היא תו רווח)

setfill(c)

קבע את דיוק השברים ל p

setprecision(p)

הפקודה:

cout << "[" << setw (6) << setfill('*') << 192;
cout << "]" << endl;
cout << hex << "[" << setw (6);
cout << setfill('*') << 192 << "]" << endl;
cout << setprecision(4) << 3.14159 << endl;

מייצרת:

[***192]
[****c0]
3.142

פלט של נקודה צפה עשוי או עשוי לא לקצץ אפסים עוקבים לא משנה איך תקבע את הדיוק -- זה תלוי במהדר.

ניתן לראות מהדוגמאות הנ"ל שאין להשתמש בשמות משתנים ושמות פונקציות מסוימים כדי להימנע מאיבוד המניפולטורים הבנויים בספריית iostream.

קלט מטופל בצורה דומה, תוך שימוש בזרימת הקלט cin ובאופרטור ההוצאה "<<". לדוגמא, הפקודה:

int i,j,k;
cin >> i >> j >> k;

תקרא שלושה מספרים שלמים מ stdin לתוך j ,i, ו k. רווח משמש בצורה אוטומטית כמפריד ומתעלמים ממנו. כאשר קוראים לתוך משתנה מחרוזת, הקלט נקרא מילה אחר מילה, כאשר המילים מופרדות ברווחים. מתעלמים מתווי רווח כאשר קוראים לתוך תו. ניתן לבטל התנהגות זו על ידי קריאה מפורשת של מחרוזות ושורות (עיין להלן). כל הטיפוסים הסטנדרטים שמטופלים על ידי cout מטופלים גם על ידי cin. ניתן להשתמש ב cin גם בתוך לולאת while שמסתיימת כאשר מבחינים ב EOF, כפי שמוצג להלן:

while (cin >> i)
    cout <<  i;

cin שובר אוטומטית קלט של מחרוזות למילים, ומסיים ב EOF.

2.3.2 קלט ופלט של קובץ

טיפול בקבצי טקסט לקלט ופלט נעשה על ידי הכללת הקובץ "fstream.h" ואז הגדרת משתנים מטיפוס ifstream ו ofstream בהתאמה. לדוגמא, התוכנית הבאה קוראת מקובץ בשם "xxx" וכותבת לקובץ בשם "yyy":

#include <iostream.h>
#include <fstream.h>
void main()
{
    char c;
    ifstream infile("xxx");
    ofstream outfile("yyy");
    if (outfile &&  infile) // They will be 0 on err.
        while (infile >> c)
            outfile <<  c;
}

המשתנים infile ו outfile מקבלים את שם הקובץ באתחול, ומשתמשים בהם בדיוק כפי שמשתמשים ב cin ו cout. אולם, קוד זה אינו פועל כמצופה, כיוון שמתעלמים מרווחים, טאבים, ותווי '0/' בסוף כל שורה כמו שטח ריק כאשר משתמשים ב >> לתו. במקום זאת, ניתן להשתמש בפונקציה "get", כפי שמוצג להלן:

while (infile.get(c))
    outfile << c;

או:

while (infile.get(c))
    outfile.put(c);

ניתן גם לקרוא שורות שלמות על ידי קריאה לפונקציה "getline" באותו אופן שבו משתמשים בפונקציה "get". כדי לפתוח קובץ לשרשור, השתמש ב:

ofstream("xxx", ios::app);

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

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

outfile.close();

2.3.3 קלט/פלט של מחרוזת

ניתן לקרוא קלט ממחרוזות בזיכרון, ולשלוח פלט למחרוזות בזיכרון, על ידי שכפול הפעולה של sscanf ו sprintf. כדי לעשות זאת, יש להכליל את הקובץ "strstream.h" ואז להצהיר על מחרוזות קלט ופלט. להלן מוצגת מחרוזת פלט:

char s[100];
ostrstream outstring(s,100);
outstring << 3.14 << " is pi" << ends;
cout << s;

המחרוזת s ממולאת בטקסט "3.14 pi is". אם s מלאה, outstring יפסיק אוטומטית להכניס לתוכה ערכים.

אם קיימת מחרוזת s ואתה רוצה לקרוא ממנה, אתה יכול להשתמש בזרימת מחרוזת קלט כפי שמוצג להלן:

char *s = "3.14  12  cat";
istrstream instring(s, strlen(s));
float f;
int i;
char t[100];
instring >> f >> i >> t;

לספריית iostream יש הרבה יכולות נוספות שלא נדונות כאן. למידע נוסף ראה את התיעוד של ++C שמסופק עם המהדר -- הוא מכיל התייחסות שלמה לספריית I/O.

2.4 הצהרות משתנים

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

{
    int i;
    ... code ...
    int j;
    ... code ...
    int k=func(i,j);
    ... code ...
}

כל שלושת המשתנים מתחילים להתקיים בנקודת ההצהרה ונעלמים בסוגריים המסולסלות הסוגרות.

2.5 קבועים

ב C יוצרים קבוע על ידי שימוש ב macro preprocessor. להלן מוצגת דוגמא:

#define MAX 100

כאשר התוכנית עוברת הידור, בעיבוד המוקדם נמצאים כל המופעים של המילה MAX ומחליף אותם במחרוזת 100.

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

const int MAX=100;

החלק int MAX=100; מפורמט בדיוק באותו אופן כמו הצהרה רגילה. המילה const לפניו פשוט מגדירה שהמשתנה MAX לא יכול להשתנות לאחר מכן.

השימוש באותיות גדולות לשמות משתנים קבועים ב C הוא מסורת שאתה עשוי לבחור לקיים או להתעלם ממנה.

ההגבלה של const יכולה גם לשמש ברשימות פרמטרים כדי לפרט את השימוש התקף של פרמטר. שלושת הפונקציות הבאות מדגימות שימושים שונים של const.

void func1(const int i)
{
    i=5;        // cannot modify a constant
}

void func2(char * const s)
{
    s="hello";  // cannot modify the pointer
}

void func3(const char * s)
{
    s="hello";  // this is OK
    *s='A';     // cannot modify what is pointed to
}

יש להשתמש תמיד בשימוש שמוצג ב func2 כשרוצים להעביר פרמטר מסוג *char.

2.6 הגדרה מחדש של פונקציות

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

#include <iostream.h> 
void func(int i)
{
    cout << "function 1 called" << endl;
    cout << "parameter = " << i << endl;
}


void func(char c)
{
    cout << "function 2 called" << endl;
    cout << "parameter = " << c << endl;
}

void func(char *s)
{
    cout << "function 3 called" << endl;
    cout << "parameter = " << s << endl;
}

void func(char *s, int i)
{
    cout << "function 4 called" << endl;
    cout << "parameter = " << s;
    cout << ", parameter = " << i << endl;
}

main()
{
    func(10);
    func('B');
    func("hello");
    func("string", 4);
    return 0;
}

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

2.7 ארגומנטים של ברירת מחדל

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

#include <iostream.h>

void sample(char *s, int i=5)
{
    cout << "parameter 1 = " << s << endl;
    cout << "parameter 2 = " << i << endl;
}

main()
{
    sample("test1");
    sample("test1",10);
    return 0;
}

הקריאה הראשונה לפונקציה תיתן כפלט את ערך ברירת המחדל 5 לפרמטר i, בעוד שהקריאה השניה תיתן את הפלט 10.

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

2.8 הקצאת זיכרון

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

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

int *p;
p = new int;
*p = 12;
cout << *p;
delete p;

ניתן גם להקצות גושים שמכילים מערכים בגדלים משתנים תוך שימוש בטכניקה דומה. שים לב לשימוש ב delete[] למחיקת המערך:

int *p;
p = new int[100];
p[10] = 12;
cout << p[10];
delete [] p;

הערך 100 יכול להיות משתנה אם רוצים.

כאשר עובדים עם טיפוסים שהוגדרו על ידי המשתמש, new עובד בדיוק באותה צורה. לדוגמא:

typedef node
{
    int data;
    node *next;
} node;

main()
{
    node *p;
    p=new node;
    p->data = 10;
    delete p;
}

במדריכים מאוחרים יותר תראה שהאופרטור delete מורכב מאוד כאשר עובדים עם מחלקות שהוגדרו על ידי המשתמש.

2.9 הצהרות התייחסות

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

void swap(int *i, int *j)
{
    int t = *i;
    *i = *j;
    *j = t;
}

main()
{
    int a=10, b=5;

    swap(& a, & b);
    cout << a << b << endl;
}

++C מספקת אופרטור התייחסות כדי לנקות מעט את התחביר. הקוד הבא עובד ב ++C:

void swap(int&  i, int&  j)
{
    int t = i;
    i = j;
    j = t;
}

main()
{
    int a=10, b=5;

    swap(a, b);
    cout << a << b << endl;
}

הפרמטרים i ו j שמוצהרים כטיפוס &int מתנהגים כהתייחסויות למספרים שלמים שמועברים (קרא את &int כ "התייחסות למספר שלם") כאשר משתנה מושם במשתנה התייחסות, ההתייחסות לוקחת את הכתובת שלו ומחקה את המיקום הממשי של המשתנה שמושם בה. לדוגמא:

int a;
int & b=a;

a=0;
b=5;
cout << a << endl;

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




לדף הראשון

<< לדף הקודם

לדף הבא >>