השלבים הבאים

מבוא לתכנות ו-C++

מדריך האינטרנט הזה ממשיך במושגים מתקדמים יותר – יש לקרוא את חלק ג'. נתמקד בשימוש במיקום הסמן ובתחילת העבודה עם אובייקטים.

למידה לפי דוגמה 2

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

דוגמה ראשונה: תרגול נוסף של פירוק כימי

מומלץ להפיק את הפלט הבא ממשחק פשוט:

Welcome to Artillery.
You are in the middle of a war and being charged by thousands of enemies.
You have one cannon, which you can shoot at any angle.
You only have 10 cannonballs for this target..
Let's begin...

The enemy is 507 feet away!!!
What angle? 25<
You over shot by 445
What angle? 15
You over shot by 114
What angle? 10
You under shot by 82
What angle? 12
You under shot by 2
What angle? 12.01
You hit him!!!
It took you 4 shots.
You have killed 1 enemy.
I see another one, are you ready? (Y/N) n

You killed 1 of the enemy.

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

StartUp(); // This displays the introductory script.
killed = 0;
do {
  killed = Fire(); // Fire() contains the main loop of each round.
  cout << "I see another one, care to shoot again? (Y/N) " << endl;
  cin >> done;
} while (done != 'n');
cout << "You killed " << killed << " of the enemy." << endl;

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

In case you are a little rusty on physics, here are the calculations:

Velocity = 200.0; // initial velocity of 200 ft/sec Gravity = 32.2; // gravity for distance calculation // in_angle is the angle the player has entered, converted to radians. time_in_air = (2.0 * Velocity * sin(in_angle)) / Gravity; distance = round((Velocity * cos(in_angle)) * time_in_air);

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

דוגמה שנייה: תרגול עם מצביעים

יש ארבעה דברים שכדאי לזכור כשעובדים עם מצביעים:
  1. מצביעים הם משתנים ששומרים כתובות בזיכרון. בזמן שתוכנית פועלת, כל המשתנים מאוחסנים בזיכרון, כל אחד בכתובת או במיקום ייחודי משלו. מצביע הוא סוג מיוחד של משתנה שמכיל כתובת זיכרון במקום ערך של נתונים. בדיוק כמו שנתונים משתנים כשמשתמשים במשתנה רגיל, הערך של הכתובת ששמורה במצביע משתנה במהלך שינוי של משתנה מצביע. לדוגמה:
    int *intptr; // Declare a pointer that holds the address
                 // of a memory location that can store an integer.
                 // Note the use of * to indicate this is a pointer variable.
    
    intptr = new int; // Allocate memory for the integer.
    *intptr = 5; // Store 5 in the memory address stored in intptr.
          
  2. בדרך כלל אנחנו אומרים שמצביע "מצביע" אל המיקום שהוא מאחסן (ה "מצביע"). כך שבדוגמה שלמעלה, intptr מפנה אל נקודה 5.

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

    int *ptr; // Declare integer pointer.
    ptr = new int; // Allocate some memory for the integer.
    *ptr = 5; // Dereference to initialize the pointee.
    *ptr = *ptr + 1; // We are dereferencing ptr in order
                     // to add one to the value stored
                     // at the ptr address.
          

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

  3. הקצאת מצביע בין שני מצביעים גורמת להם להצביע לאותו אדם. לכן, במטלה y = x; הפונקציה y מצביעה על אותו נקודת נקודה כמו x. הקצאת המצביע לא נוגעת בנקודה. הוא פשוט משנה מיקום של מצביע אחד, למיקום של מצביע אחר. אחרי הקצאת הסמן, שתי הנקודות "משתפים" את האדם. 
  4. void main() {
     int* x; // Allocate the pointers x and y
     int* y; // (but not the pointees).
    
     x = new int; // Allocate an int pointee and set x to point to it.
    
     *x = 42; // Dereference x and store 42 in its pointee
    
     *y = 13; // CRASH -- y does not have a pointee yet
    
     y = x; // Pointer assignment sets y to point to x's pointee
    
     *y = 13; // Dereference y to store 13 in its (shared) pointee
    }
      

הנה עקבות של הקוד הזה:

1. מקצים שני מצביעים x ו-y. הקצאת הסמן לא מאפשרת להקצות נקודות.
2. מקצה נקודת קצה ומגדירים x יפנה אליה.
3. מסירים את ההפניה x לאחסון של גרסה 42 בנקודה שלה. זוהי דוגמה בסיסית לפעולת הביטול. מתחילים ב-x ועוקבים אחרי החץ כדי לגשת למצביע שלו.
4. כדאי לנסות להפחית את האזכור y לאחסון את המספר 13 בנקודה שלו. קריסת מחשב זו כי ל-y אין נקודה -- מעולם לא הוקצה לו.
5. מקצים y = x; כך ש-y יפנה אל הנקודה של x. עכשיו x ו y מצביעים על אותו נקודה – הם 'משתפים'.
6. כדאי לנסות להפחית את האזכור y לאחסון את המספר 13 בנקודה שלו. הפעם זה עבד, כי המטלה הקודמת העניקה נקודות.

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

int my_int = 46; // Declare a normal integer variable.
                 // Set it to equal 46.

// Declare a pointer and make it point to the variable my_int
// by using the address-of operator.
int *my_pointer = &my_int;

cout << my_int << endl; // Displays 46.

*my_pointer = 107; // Derefence and modify the variable.

cout << my_int << endl; // Displays 107.
cout << *my_pointer << endl; // Also 107.

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

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

int *ptr1; // Declare a pointer to int.
ptr1 = new int; // Reserve storage and point to it.

float *ptr2 = new float; // Do it all in one statement.

delete ptr1; // Free the storage.
delete ptr2;

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

// Passing parameters by reference.
#include <iostream>
using namespace std;

void Duplicate(int& a, int& b, int& c) {
  a *= 2;
  b *= 2;
  c *= 2;
}

int main() {
  int x = 1, y = 3, z = 7;
  Duplicate(x, y, z);
  // The following outputs: x=2, y=6, z=14.
  cout << "x="<< x << ", y="<< y << ", z="<< z;
  return 0;
}

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

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

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

void Duplicate(int *a, int *b, int *c) {
  *a *= 2;
  *b *= 2;
  *c *= 2;
}

int main() {
  int x = 1, y = 3, z = 7;
  Duplicate(&x, &y, &z);
  // The following outputs: x=2, y=6, z=14.
  cout << "x=" << x << ", y=" << y << ", z=" << z;
  return 0;
}

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

מה מתקבל הפלט של התוכנית הבאה? ציירו איור של זיכרון כדי להבין אותו.

void DoIt(int &foo, int goo);

int main() {
  int *foo, *goo;
  foo = new int;
  *foo = 1;
  goo = new int;
  *goo = 3;
  *foo = *goo + 3;
  foo = goo;
  *goo = 5;
  *foo = *goo + *foo;
  DoIt(*foo, *goo);
  cout << (*foo) << endl;
}

void DoIt(int &foo, int goo) {
  foo = goo + 3;
  goo = foo + 4;
  foo = goo + 3;
  goo = foo;
} 

מפעילים את התוכנית כדי לבדוק אם קיבלת את התשובה הנכונה.

דוגמה 3: העברת ערכים לפי הפניה

כותבים פונקציה שנקראת accelerate() , שמקבלת כקלט את מהירות הרכב ואת הסכום. הפונקציה מוסיפה את המהירות למהירות כדי להאיץ את הרכב. פרמטר המהירות צריך לעבור לפי הפניה וסכום לפי ערך. כאן אפשר למצוא את הפתרון שלנו.

דוגמה 4: מחלקות וחפצים

חשוב על הקורס הבא:

// time.cpp, Maggie Johnson
// Description: A simple time class.

#include <iostream>
using namespace std;

class Time {
 private:
  int hours_;
  int minutes_;
  int seconds_;
 public:
  void set(int h, int m, int s) {hours_ = h; minutes_ = m; seconds_ = s; return;}
  void increment();
  void display();
};

void Time::increment() {
  seconds_++;
  minutes_ += seconds_/60;
  hours_ += minutes_/60;
  seconds_ %= 60;
  minutes_ %= 60;
  hours_ %= 24;
  return;
}

void Time::display() {
  cout << (hours_ % 12 ? hours_ % 12:12) << ':'
       << (minutes_ < 10 ? "0" :"") << minutes_ << ':'
       << (seconds_ < 10 ? "0" :"") << seconds_
       << (hours_ < 12 ? " AM" : " PM") << endl;
}

int main() {
  Time timer;
  timer.set(23,59,58);
  for (int i = 0; i < 5; i++) {
    timer.increment();
    timer.display();
    cout << endl;
  }
}

שימו לב שבמשתנים של חברי הכיתה מופיע קו תחתון. המטרה היא להבחין בין משתנים מקומיים למשתני מחלקה.

הוספת שיטת הפחתה למחלקה הזו. כאן אפשר למצוא את הפתרון שלנו.

פלאי המדע: מדעי המחשב

תרגילים

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

חשוב לזכור שזו תוכנית טובה...

... מחולקת באופן לוגי לפונקציות שבהן כל פונקציה מבצעת משימה אחת בלבד.

... כולל תוכנית ראשית שכתוב בה קווים כלליים של מטרת התוכנית.

... יש שם פונקציה תיאורית, קבוע ומשתנה.

... משתמשת בקבועים כדי להימנע מנתוני "קסם" בתוכנית.

... יש ממשק משתמש ידידותי.

תרגילי חימום

  • תרגיל 1

    למספר השלם 36 יש תכונה ייחודית: הוא ריבוע מושלם, והוא גם סכום המספרים השלמים מ-1 עד 8. המספר הבא אחריה הוא 1225, שהוא 352, וסכום המספרים השלמים מ-1 עד 49. מוצאים את המספר הבא שהוא ריבוע מושלם וגם את הסכום של סדרה 1...n. המספר הבא עשוי להיות גדול מ-32,767. אפשר להשתמש בפונקציות ספרייה שאתם מכירים (או בנוסחאות מתמטיות) כדי לגרום לתוכנה לפעול מהר יותר. אפשר גם לכתוב את התוכנית הזו באמצעות for-loops כדי לקבוע אם מספר הוא ריבוע מושלם או סכום של סדרה. (הערה: בהתאם למכשיר ולתוכנה שלך, יכול להיות שיעבור זמן מה עד שהמספר הזה יימצא.)

  • תרגיל 2

    חנות הספרים של המכללה זקוקה לעזרה שלך בהערכת העסק לשנה הבאה. הניסיון הראה שמכירת ספרים תלויה מאוד בשאלה אם ספר חובה לקורס או רק אופציונלי, ואם כבר השתמשו בו בכיתה בעבר. ספר לימוד חדש וחובה יימכר ל-90% מכלל הנרשמים, אבל אם כבר השתמשו בו בכיתה בעבר, רק 65% יקנו אותו. באופן דומה, 40% מהנרשמים הפוטנציאליים יקנו ספר לימוד חדש ואופציונלי, אבל אם השתמשו בו בכיתה לפני שרק 20% יקנו אותו. (שימו לב שהמונח "משומש" כאן לא מתייחס לספרים יד שנייה.)

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

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

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

    לפניכם פלט לדוגמה:

    Please enter the book code: 1221
     single copy price: 69.95
     number on hand: 30
     prospective enrollment: 150
     1 for reqd/0 for optional: 1
     1 for new/0 for used: 0
    ***************************************************
    Book: 1221
    Price: $69.95
    Inventory: 30
    Enrollment: 150
    
    This book is required and used.
    ***************************************************
    Need to order: 67
    Total Cost: $4686.65
    ***************************************************
    
    Enter 1 to do another book, 0 to stop. 0
    ***************************************************
    Total for all orders: $4686.65
    Profit: $937.33
    ***************************************************

פרויקט מסד נתונים

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

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

  • היכולת להוסיף מלחין חדש
  • היכולת לדרג מלחין (כלומר, לציין עד כמה אנחנו אוהבים או לא אוהבים את המוזיקה של המלחין)
  • היכולת להציג את כל המלחינים במסד הנתונים
  • היכולת להציג את כל המלחינים לפי דירוג

"יש שתי דרכים לבנות את תכנון התוכנה: דרך אחת היא לפשט את הבנייה כך שברור שאין חסרונות, והדרך השנייה היא לסבך את הפרויקט כך שלא יהיו ליקויים ברורים. השיטה הראשונה היא הרבה יותר קשה." - C.A.R. Hoare

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

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

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

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

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

עיצוב OO מורכב מקבוצת מחלקות, הנתונים המשויכים למחלקות האלה וקבוצת הפעולות שהמחלקות יכולות לבצע. אנחנו גם צריכים לזהות את הדרכים שבהן כיתות שונות מקיימים אינטראקציה. האינטראקציה הזו יכולה להתבצע על ידי אובייקטים של מחלקה שמפעילים את הפעולות של אובייקטים של מחלקות אחרות. לדוגמה, אנחנו יכולים ליצור מחלקה AppleOutputer כדי ליצור פלט של הצבע והטעם של מערך אובייקטים של Apple, על ידי קריאה לשיטה Display() של כל אובייקט של Apple.

אלה השלבים לביצוע עיצוב OO:

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

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

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

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

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

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

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

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

המחלקה 'ממשק משתמש' מטמיעה ממשק מבוסס-תפריט עם גורמי handler שקוראים לפעולות במחלקה Database. 

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

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

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

// composer.h, Maggie Johnson
// Description: The class for a Composer record.
// The default ranking is 10 which is the lowest possible.
// Notice we use const in C++ instead of #define.
const int kDefaultRanking = 10;

class Composer {
 public:
  // Constructor
  Composer();
  // Here is the destructor which has the same name as the class
  // and is preceded by ~. It is called when an object is destroyed
  // either by deletion, or when the object is on the stack and
  // the method ends.
  ~Composer();

  // Accessors and Mutators
  void set_first_name(string in_first_name);
  string first_name();
  void set_last_name(string in_last_name);
  string last_name();
  void set_composer_yob(int in_composer_yob);
  int composer_yob();
  void set_composer_genre(string in_composer_genre);
  string composer_genre();
  void set_ranking(int in_ranking);
  int ranking();
  void set_fact(string in_fact);
  string fact();

  // Methods
  // This method increases a composer's rank by increment.
  void Promote(int increment);
  // This method decreases a composer's rank by decrement.
  void Demote(int decrement);
  // This method displays all the attributes of a composer.
  void Display();

 private:
  string first_name_;
  string last_name_;
  int composer_yob_; // year of birth
  string composer_genre_; // baroque, classical, romantic, etc.
  string fact_;
  int ranking_;
};

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

// database.h, Maggie Johnson
// Description: Class for a database of Composer records.
#include  <iostream>
#include "Composer.h"

// Our database holds 100 composers, and no more.
const int kMaxComposers = 100;

class Database {
 public:
  Database();
  ~Database();

  // Add a new composer using operations in the Composer class.
  // For convenience, we return a reference (pointer) to the new record.
  Composer& AddComposer(string in_first_name, string in_last_name,
                        string in_genre, int in_yob, string in_fact);
  // Search for a composer based on last name. Return a reference to the
  // found record.
  Composer& GetComposer(string in_last_name);
  // Display all composers in the database.
  void DisplayAll();
  // Sort database records by rank and then display all.
  void DisplayByRank();

 private:
  // Store the individual records in an array.
  Composer composers_[kMaxComposers];
  // Track the next slot in the array to place a new record.
  int next_slot_;
};

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

כשתתחילו לעבוד על ההטמעה של המחלקות Composer ו-Database, יהיה הרבה יותר נקי ממחלקה נפרדת של Composer. בפרט, פעולות אטומיות נפרדות באובייקט Composer מפשטות מאוד את ההטמעה של ה-methods של Display() במחלקה של Database.

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

כדי לדעת אם אתם מסיתים יותר מדי או פחות מדי, אפשר לתכנן את הכיתות בצורה קפדנית. כפי שצוין קודם, חשוב לתכנן את עיצוב הכיתה לפני שתתחילו לתכנת, כדי שתוכלו לנתח את הגישה שלכם. דוגמה נפוצה למטרה הזו היא UML (Unified Modeling Language) - עכשיו, אחרי שהגדרנו את המחלקות לאובייקטים של Composer ו-Database, אנחנו זקוקים לממשק שמאפשר למשתמש ליצור אינטראקציה עם מסד הנתונים. תפריט פשוט יפתור את הבעיה:

Composer Database
---------------------------------------------
1) Add a new composer
2) Retrieve a composer's data
3) Promote/demote a composer's rank
4) List all composers
5) List all composers by rank
0) Quit

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

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

// test_composer.cpp, Maggie Johnson
//
// This program tests the Composer class.

#include <iostream>
#include "Composer.h"
using namespace std;

int main()
{
  cout << endl << "Testing the Composer class." << endl << endl;

  Composer composer;

  composer.set_first_name("Ludwig van");
  composer.set_last_name("Beethoven");
  composer.set_composer_yob(1770);
  composer.set_composer_genre("Romantic");
  composer.set_fact("Beethoven was completely deaf during the latter part of "
    "his life - he never heard a performance of his 9th symphony.");
  composer.Promote(2);
  composer.Demote(1);
  composer.Display();
}

אנחנו זקוקים לתוכנת בדיקה דומה למחלקה של מסד הנתונים.

// test_database.cpp, Maggie Johnson
//
// Description: Test driver for a database of Composer records.
#include <iostream>
#include "Database.h"
using namespace std;

int main() {
  Database myDB;

  // Remember that AddComposer returns a reference to the new record.
  Composer& comp1 = myDB.AddComposer("Ludwig van", "Beethoven", "Romantic", 1770,
    "Beethoven was completely deaf during the latter part of his life - he never "
    "heard a performance of his 9th symphony.");
  comp1.Promote(7);

  Composer& comp2 = myDB.AddComposer("Johann Sebastian", "Bach", "Baroque", 1685,
    "Bach had 20 children, several of whom became famous musicians as well.");
  comp2.Promote(5);

  Composer& comp3 = myDB.AddComposer("Wolfgang Amadeus", "Mozart", "Classical", 1756,
    "Mozart feared for his life during his last year - there is some evidence "
    "that he was poisoned.");
  comp3.Promote(2);

  cout << endl << "all Composers: " << endl << endl;
  myDB.DisplayAll();
}

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

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

Composer Database
---------------------------------------------
1) Add a new composer
2) Retrieve a composer's data
3) Promote/demote a composer's rank
4) List all composers
5) List all composers by rank
0) Quit

השתמש בשיטות שהגדרת במחלקה Database כדי להטמיע את ממשק המשתמש. לוודא שהשיטות חסינות שגיאות. לדוגמה, דירוג צריך להיות תמיד בטווח של 1-10. אף אחד לא יוכל להוסיף 101 מלחינים, אלא אם בכוונתך לשנות את מבנה הנתונים במחלקה Database.

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

  • כל תוכנית שאנחנו כותבים מתחילה בהערת כותרת, שכוללת את שם המחבר, פרטים ליצירת קשר, תיאור קצר ושימוש (אם רלוונטי). כל פונקציה/שיטה מתחילה בהערה על פעולה ושימוש.
  • אנחנו מוסיפים הסברים באמצעות משפטים שלמים, בכל פעם שהקוד לא מתעד את עצמו, לדוגמה במקרים שבהם העיבוד מסורבל, לא ברור, מעניין או חשוב.
  • יש להשתמש תמיד בשמות תיאוריים: משתנים הם מילים באותיות קטנות שמופרדות על ידי _, כמו ב-my_variable. בשמות פונקציות/שיטות, נעשה שימוש באותיות רישיות כדי לסמן מילים, כמו ב-MyExciteFunction(). קבועים מתחילים באות k והם משתמשים באותיות רישיות כדי לסמן מילים, כמו ב-kDaysInWeek.
  • כניסת הפיסקה מופיעה בכפולות של שתיים. הרמה הראשונה כוללת שני רווחים. אם יש צורך בכניסת פיסקה נוספת, אנחנו משתמשים בארבעה רווחים, בשישה רווחים וכו'.

ברוך בואך לעולם האמיתי!

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

קבצים

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

נעשה שימוש נפוץ בכלי build בקוד פתוח שנקרא "Make". כדי לקבל מידע נוסף, אפשר לעיין במאמר הזה. מומלץ לבדוק אם אפשר ליצור תרשים תלות עבור אפליקציית Composer Database, ואז לתרגם אותו ל-Makefile.זה הפתרון שלנו.

מערכות לניהול הגדרות

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

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

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

  1. יוסי פותח את המסמך במחשב שלו ועובד בקטע שלו.
  2. שוש פותחת את המסמך במחשב שלה ועובדת בקטע שלה.
  3. חיים משלים את השינויים שלו ושומר את המסמך במחשב האחסון.
  4. שוש משלימה את השינויים ושומרת את המסמך במחשב האחסון.

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

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

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

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

ניהול הגדרות התוכנה

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

כל מערכות ה-SCM מספקות את התכונות החיוניות הבאות:

  • ניהול בו-זמנית
  • ניהול גרסאות
  • סנכרון

נבחן לעומק כל אחת מהתכונות האלה.

ניהול בו-זמנית

המונח 'concurrency' מתייחס לעריכה בו-זמנית של קובץ על ידי יותר מאדם אחד. אנחנו רוצים שאנשים יוכלו לעשות את זה עם מאגר גדול, אבל הדבר עלול לגרום לבעיות מסוימות.

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

  1. Client1 פותח את bar.cpp.
  2. Client2 פותח את bar.cpp.
  3. לקוח1 משנה את הקובץ ושומר אותו.
  4. לקוח 2 משנה את הקובץ ושומר אותו מחליף את השינויים של Client1.

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

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

ניהול גרסאות

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

סנכרון

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

חתירה

Subversion (SVN) היא מערכת לניהול גרסאות בקוד פתוח. הוא כולל את כל התכונות שתוארו למעלה.

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

במהלך הקורס הזה נשתמש ב-SVN כדי לעזור לך להכיר את ניהול ההגדרות האישיות. מערכות כאלה נפוצות מאוד בתעשייה.

השלב הראשון הוא התקנת SVN במערכת. לקבלת הוראות, לכאן יש הוראות. מוצאים את מערכת ההפעלה ומורידים את הקובץ הבינארי המתאים.

מונחים מסוימים ב-SVN

  • גרסה: שינוי בקובץ או בקבוצת קבצים. תיקון הוא "תמונת מצב" בפרויקט שמשתנה כל הזמן.
  • מאגר: העותק המאסטר שבו נשמרת ב-SVN היסטוריית הגרסאות המלאה של הפרויקט. לכל פרויקט יש מאגר אחד.
  • עותק עבודה: העותק שבו מהנדס מבצע שינויים בפרויקט. יכולים להיות עותקים רבים של פרויקט נתון, שכל אחד מהם נמצא בבעלות של מהנדס תוכנה.
  • תשלום: כדי לבקש עותק פעיל מהמאגר. עותק פעיל זהה למצב הפרויקט בזמן התשלום.
  • Commit: כדי לשלוח שינויים מעותק העבודה שלכם למאגר המרכזי. נקרא גם צ'ק-אין או שליחה.
  • עדכון: כדי להעביר שינויים של אנשים אחרים מהמאגר לעותק העבודה שלכם, או כדי לציין אם יש שינויים ללא התחייבות בעותק הפעיל. המצב הזה זהה לסנכרון, כפי שתואר למעלה. לכן, האפשרות 'עדכון/סנכרון' עדכנת את העותק הפעיל של העותק של המאגר.
  • התנגשות: מצב שבו שני מהנדסים מנסים לבצע שינויים באותו אזור בקובץ. SVN מצביע על התנגשויות, אבל המהנדסים חייבים לפתור אותן.
  • הודעה ביומן: תגובה שמצרפים לגרסה הקודמת בזמן שמצרפים אותה, שמתארת את השינויים שביצעתם. ביומן מוצג סיכום של מה שקורה בפרויקט.

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

$ svnadmin create /usr/local/svn/newrepos
$ svn import mytree file:///usr/local/svn/newrepos/project -m "Initial import"
Adding         mytree/foo.c
Adding         mytree/bar.c
Adding         mytree/subdir
Adding         mytree/subdir/foobar.h

Committed revision 1.

פקודת הimport תעתיק את התוכן של ספריית mytree לפרויקט הספרייה שבמאגר. תוכלו לעיין בספרייה שבמאגר באמצעות הפקודה list

$ svn list file:///usr/local/svn/newrepos/project
bar.c
foo.c
subdir/

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

$ svn checkout file:///usr/local/svn/newrepos/project
A    foo.c
A    bar.c
A    subdir
A    subdir/foobar.h
…
Checked out revision 215.

עכשיו שיש לך עותק פעיל, אפשר לבצע בו שינויים בקבצים ובספריות. עותק העבודה שלך הוא בדיוק כמו כל אוסף אחר של קבצים וספריות - אפשר להוסיף קבצים חדשים, לערוך אותם, להעביר אותם ממקום למקום ואפילו למחוק את כל העותק הפעיל. שימו לב שאם מעתיקים ומעבירים קבצים בעותק העבודה, חשוב להשתמש ב-svn copy וב-svnmove במקום בפקודות של מערכת ההפעלה. כדי להוסיף קובץ חדש צריך להשתמש ב-svn add וכדי למחוק קובץ, צריך להשתמש ב-svn delete. אם ברצונך לערוך רק לערוך את הקובץ, עליך לפתוח את הקובץ באמצעות העורך שלך ולערוך את הקובץ.

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

$ svn list file:///usr/local/svn/repos
/trunk
/branches

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

A       subdir/new.h      # file is scheduled for addition
D       subdir/old.c        # file is scheduled for deletion
M       bar.c                  # the content in bar.c has local modifications

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

$ svn diff bar.c
Index: bar.c
===================================================================
--- bar.c	(revision 5)
+++ bar.c	(working copy)
## -1,18 +1,19 ##
+#include
+#include

 int main(void) {
-  int temp_var;
+ int new_var;
...

לסיום, כדי לעדכן את עותק העבודה מהמאגר, משתמשים בפקודה svn update.

$ svn update
U  foo.c
U  bar.c
G  subdir/foobar.h
C  subdir/new.h
Updated to revision 2.

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

לכל קובץ שיש בו התנגשות, התכונה Subversion מעבירה שלושה קבצים לעותק העבודה:

  • file.mine: זה הקובץ כפי שהיה קיים בעותק העבודה לפני שעדכנת את עותק העבודה.
  • file.rOLDREV: זה הקובץ שביטלת מהמאגר לפני שביצעת את השינויים.
  • file.rNEWREV: קובץ זה הוא הגרסה הנוכחית במאגר.

ניתן לבצע אחת משלוש הפעולות הבאות כדי לפתור את ההתנגשות:

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

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

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

svn commit -m "Update files to include new headers."  

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

לתרגול, אפשר ליצור מאגר למערכת Composer Database, ולייבא את כל הקבצים שלכם. לאחר מכן, קוראים עותק פעיל ומבצעים את הפקודות שמתוארות למעלה.

קובצי עזר

ספר מכירות אונליין

מאמר בוויקיפדיה על SVN

אתר חתירה

בקשה: מחקר באנטומיה

כדאי לצפות בסרט eSkeletons מאוניברסיטת טקסס באוסטין