מבוא לתכנות ו-C++
המדריך המקוון הזה ממשיך לכלול מושגים מתקדמים יותר – מומלץ לקרוא את חלק 3. במודול הזה נתמקד בשימוש בסמנים ותחילת העבודה עם אובייקטים.
למידה מדוגמה מס' 2
ביחידת הלימוד הזו נתמקד בתרגול נוסף של פירוק כימי, הבנת סמנים ותחילת העבודה עם אובייקטים וכיתות. אפשר להיעזר בדוגמאות הבאות. כשתתבקשו, תוכלו לכתוב את התוכניות בעצמכם או לבצע את הניסויים. חשוב לנו להדגיש שהמפתח למתכנת טוב הוא תרגול, תרגול ותרגול!
דוגמה 1: שיטות נוספות של פירוק כימי
נבחן את הפלט הבא ממשחק פשוט:
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(), יהיה עליכם לכלול את conversion.h. אני רוצה לנסות לכתוב את התוכנית הזו - זו שיטה מצוינת לפרק בעיות הוא סקירה בסיסית של C++. זכרו לבצע משימה אחת בלבד בכל פונקציה. כאן היא התוכנית המתוחכמת ביותר שכתבנו עד עכשיו, לכן היא עשויה לקחת הגיע הזמן לעשות את זה.כאן מופיע הפתרון שלנו.
דוגמה שנייה: תרגול עם מצביעים
יש ארבעה דברים שחשוב לזכור כשעובדים עם מצביעים:- מצביעים הם משתנים ששומרים כתובות זיכרון. במהלך ההפעלה של התוכנית,
כל המשתנים מאוחסנים בזיכרון, לכל אחד מהם כתובת או מיקום ייחודיים לו.
מצביע הוא סוג מיוחד של משתנה שמכיל כתובת של זיכרון
יותר מערך נתונים. בדיוק כמו שהנתונים משתנים כשמשתמשים במשתנה רגיל,
ערך הכתובת ששמורה בסמן משתנה כמשתנה של מצביע
עובר מניפולציה. לדוגמה:
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.
- בדרך כלל אנחנו אומרים שהסמן למיקום שהיא מאחסנת
("הנקודה הפוכה"). אז בדוגמה שלמעלה, 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++ מתחילים לעבוד עם מצביעים שוכחים לאתחל פוינט. לפעמים הדבר עלול לגרום לקריסה בסביבת זמן הריצה כי אנחנו ניגשים מקום בזיכרון שמכיל נתונים לא ידועים. אם ננסה לשנות את זה, נתונים, אנחנו יכולים לגרום לפגיעה קלה בזיכרון, כך שיהיה באג קשה לאיתור.
- הקצאת מצביע בין שני סימנים גורמת להם להפנות לאותו מצביע.
כלומר המטלה y = x; שמפנה y לאותו נקודה שמסומנת ב-x. הקצאת מצביע
לא נוגע במחודד. היא רק משנה מצביע אחד כדי לראות את אותו המיקום
בתור מצביע נוסף. אחרי הקצאת הסמן, שני המצביעים 'משתפים' ה
נקודה.
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 בנקודה שלו. זה קורס כי שאין לו פוינט - מעולם לא הוקצה לו פרס. | ![]() |
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" . הכרזנו על משתנה של מספר שלם נורמלי ושינינו אותו באמצעות סמנים.
בדוגמה הזו נמחיש את השימוש באופרטור מחיקה שמבטל הקצאה זיכרון ערימה, ואיך נוכל להקצות למבנים מורכבים יותר. נסביר על וארגון הזיכרון (ערימה של ערימה, ערימה של זמן ריצה) בשיעור אחר. בינתיים, רק הערימה היא מאגר של זיכרון חופשי שזמין לתוכנות.
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; }
אם משאירים את ה- & s מחוץ לארגומנטים בהגדרת הפונקציה הכפולה, אנחנו מעבירים את המשתנים 'לפי ערך'. כלומר, נוצר עותק מערך של של המשתנה. כל שינוי שעושים במשתנה בפונקציה ישנה את העותק. הם לא משנים את המשתנה המקורי.
כשמשתנה מועבר בהפניה, אנחנו לא מעבירים עותק של הערך שלו, אנחנו מעבירים את הכתובת של המשתנה לפונקציה. כל שינוי שעושים למשתנה המקומי משנה בפועל את המשתנה המקורי שמועבר.
אם אתם מתכנתים C, מדובר בגרסה חדשה. נוכל לעשות את אותו הדבר גם ב-C הצהרה על כפילות() as כפילות(int *x), במקרה כזה, x מצביע אל מספר Int, ואז קורא ל-Smart() עם הארגומנט &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. המספר הבא יכול להיות גדול מ-32767. אתם יכולים להשתמש בפונקציות של הספרייה שאתם מכירים, (או נוסחאות מתמטיות) כדי לגרום לתוכנה לפעול מהר יותר. אפשר גם אם תכתבו את הפונקציה הזו באמצעות תווי 'לולאה' כדי לקבוע אם מספר הוא מושלם בריבוע או סכום בסדרה. (הערה: בהתאם למכונה ולתוכנה שלכם, יכול לקחת קצת זמן למצוא את המספר).
- תרגיל 2
חנות הספרים שלך במכללה זקוקה לעזרתך בהערכת הפעילות העסקית שלה לשנה. הניסיון הראה שהמכירות תלויות במידה רבה בזמינות של ספר לכל קורס, או רק אופציונלי, והאם נעשה או לא נעשה בו שימוש בכיתה. לפני. ספר לימוד חדש ונדרש יימכר ל-90% מהנרשמים הפוטנציאליים, אבל אם הוא כבר היה בשימוש בכיתה בעבר, רק 65% יקנו. באופן דומה, 40% מהנרשמים הפוטנציאליים יקנו ספר לימוד חדש ואופציונלי, נמצא בשימוש בכיתה לפני שרק 20% יקנו. (לתשומת ליבך, כאן המילה 'משומש' לא מציין ספרים יד שנייה.)
כתיבת תוכנית שמקבלת כקלט סדרת ספרים (עד שהמשתמש נכנס סנטינל). עבור כל ספר מבקשים: קוד לספר, עלות עותק אחד עבור את הספר, את המספר הנוכחי של ספרים שנמצאים כרגע באפליקציה, את המספר הפוטנציאלי של הכיתה ונתונים שמציינים אם הספר הוא חובה/אופציונלי, חדש/משומש בעבר. בתור הפלט, מציגים את כל פרטי הקלט במסך בעיצוב תקין כמה ספרים צריך להזמין (אם בכלל, שימו לב שרק ספרים חדשים מוזמנים), העלות הכוללת של כל הזמנה.
לאחר השלמת כל הקלט, מציגים את העלות הכוללת של כל הזמנות הספרים, הרווח הצפוי אם החנות משלמת 80% ממחיר המחירון. מכיוון שעדיין לא דיברנו על דרכים להתמודדות עם כמות גדולה של נתונים שנכנסים לתוכנית (הישארו) לכוונן!), פשוט לעבד ספר אחד בכל פעם ולהציג את מסך הפלט של אותו ספר. לאחר מכן, כשהמשתמש יסיים להזין את כל הנתונים, הפלט של התוכנה אמור להיות תקין את הערך הכולל ואת הערך של הרווח.
לפני שתתחילו לכתוב קוד, כדאי לחשוב על העיצוב של התוכנית. מפרקים לקבוצת פונקציות ויוצרים פונקציית Main() קוראת כך: תיאור כללי של הפתרון שלכם לבעיה. צריך לוודא שכל פונקציה מבצעת משימה אחת.
הנה פלט לדוגמה:
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. עם זאת, רבים מהאובייקטים האלה זהים במהותם. מה כדאי לעשות?
בתרשים הזה הגדרנו שני אובייקטים מהמחלקה של Apple. לכל אובייקט יש אותם מאפיינים ופעולות כמו למחלקה, אבל לאובייקט שמגדיר את המאפיינים לסוג מסוים של תפוח. בנוסף, רשת המדיה מציג את המאפיינים של האובייקט המסוים, "ירוק" ו-"Sour".
עיצוב OO כולל קבוצה של מחלקות, הנתונים המשויכים למחלקות האלה, ואת קבוצת הפעולות שהכיתות יכולות לבצע. אנחנו צריכים גם לזהות את האינטראקציה בין כיתות שונות. את האינטראקציה הזו אפשר לבצע של כיתה שמפעילה פעולות של אובייקטים ממחלקות אחרות. לדוגמה, אנחנו יכול להיות מחלקה של AppleOutputer שמפיקה את הצבע והטעם של מערך של אובייקטים של Apple, על ידי קריאה ל-method() של כל אובייקט Apple.
אלה השלבים שאנחנו מבצעים בעיצוב OO:
- צריך לזהות את המחלקות ולהגדיר באופן כללי מה האובייקט של כל מחלקה מאחסנת כנתונים ואת מה שאובייקט יכול לעשות.
- להגדיר את רכיבי הנתונים של כל מחלקה
- הגדרת הפעולות של כל כיתה והסבר על פעולות מסוימות בכיתה אחת
שהוטמעו באמצעות פעולות של מחלקות קשורות אחרות.
במערכת גדולה, השלבים האלה מתרחשים באופן איטרטיבי ברמות פירוט שונות.
למערכת מסדי הנתונים של המלחין, אנחנו זקוקים למחלקה Composer, שכוללת את כל את הנתונים שאנחנו רוצים לאחסן לגבי מלחין ספציפי. אובייקט בסיווג הזה יכול לקדם או להוריד את עצמו (לשנות את הדירוג שלו) ולהציג את מאפייניו.
אנחנו צריכים גם אוסף של אובייקטים מסוג Composer. בשביל זה אנחנו מגדירים מחלקה של מסד נתונים שמנהל את הרשומות הנפרדות. אובייקט במחלקה הזו יכול להוסיף או לאחזר להרכיב אובייקטים, ולהציג אובייקטים בודדים על ידי הפעלת פעולת התצוגה של אובייקט Composer.
לבסוף, אנחנו צריכים סוג של ממשק משתמש כדי לספק פעולות אינטראקטיביות במסד הנתונים. זוהי מחלקה של ערכי placeholder, כלומר, אנחנו באמת לא יודעים מה ממשק משתמש ייראה עדיין, אבל אנחנו יודעים שנצטרך אותו. אולי היא תהיה גרפית, אולי מבוססת טקסט. בינתיים נגדיר placeholder שנוכל למלא אחר כך.
עכשיו, אחרי שזיהינו את המחלקות לאפליקציה של מסד הנתונים של המלחינים, השלב הבא הוא להגדיר את המאפיינים והפעולות של המחלקות. בטווח והיא נחשבת עם עיפרון ונייר UML או כרטיסי CRC או OOD כדי למפות את היררכיית הכיתה ואת האינטראקציה בין האובייקטים.
במסד נתוני המלחינים שלנו, אנחנו מגדירים מחלקת Composer שכוללת את הערך הרלוונטי. הנתונים שאנחנו רוצים לאחסן על כל מלחין. הוא מכיל גם שיטות לביצוע מניפולציות של הדירוגים, והצגת הנתונים.
המחלקה Databaser זקוקה למבנה מסוים כדי להחזיק אובייקטים של Composer. אנחנו צריכים שתהיה לנו אפשרות להוסיף למבנה אובייקט Composer חדש, וגם מאחזרים אובייקט Composer ספציפי. רוצים להציג גם את כל האובייקטים לפי סדר הכניסה או לפי דירוג.
המחלקה בממשק המשתמש מממשת ממשק מבוסס-תפריט, עם handlers ש פעולות קריאה במחלקה Database.
אם קל להבין את הכיתות והמאפיינים והפעולות שלהן ברורים, כמו באפליקציית המלחין, קל יחסית לעצב את הכיתות. אבל אם יש לכם שאלות לגבי הקשרים והאינטראקציה בין השיעורים, מומלץ לשרטט אותו קודם ולעבוד על הפרטים לפני שמתחילים לקוד.
אחרי שנקבל תמונה ברורה של העיצוב ונבחן אותו (בהמשך מפורט מידע נוסף בנושא בקרוב), אנחנו מגדירים את הממשק לכל מחלקה. אין צורך לדאוג לגבי ההטמעה בשלב הזה, מהם המאפיינים והפעולות, ואילו חלקים של כיתה המצבים והפעולות זמינים למחלקות אחרות.
ב-C++ בדרך כלל אנחנו עושים זאת על-ידי הגדרת קובץ כותרת לכל כיתה. המלחין בכיתה יש חברים עם מידע פרטי, לכל הנתונים שאנחנו רוצים לשמור במלחין. אנחנו צריכים רכיבי גישה (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 היא ישרה.
// 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, וניגש אליה ישירות שם. אבל זה יהיה 'under-objectification', כלומר אנחנו לא יוצרים מודלים עם אובייקטים באותה מידה שיכולנו.
אפשר לראות את זה בתחילת העבודה על ההטמעה של ה-Composer וה-Database הרבה יותר נקי כשיש מחלקה נפרדת של Composer. באופן ספציפי, פעולות אטומיות נפרדות באובייקט Composer מפשטות מאוד את ההטמעה של methods של Display() במחלקה Database.
כמובן, יש גם דבר כזה כמו "התבטאות יתר באובייקט". איפה אנחנו מנסים להפוך הכול לכיתה, או שיש לנו יותר כיתות ממה שאנחנו צריכים. נדרש כדי למצוא את האיזון הנכון, ותגלו שהמתכנתים האישיים יהיו דעות שונות.
כדי להחליט אם אתם יותר מדי או מתנגדים לאובייקט, אפשר בדרך כלל למיין אותם ליצור דיאגרמות של הכיתות. כמו שאמרנו קודם, חשוב לאמן שיעור לפני שמתחילים לתכנת, וזה יכול לעזור לכם לנתח את הגישה שלכם. למטרה הזו, UML (שפת יצירת מודלים מאוחדת) עכשיו, אחרי שהגדירו את המחלקות לאובייקטים 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, כלומר, אם נרצה ליצור ממשק משתמש גרפי בשלב כלשהו, ולא חייבים לשנות שום דבר במערכת מלבד בממשק המשתמש.
הדבר האחרון שאנחנו צריכים להשלים את הגשת הבקשה הוא תוכנית לבדיקת הכיתות. במחלקה Composer, אנחנו רוצים שתוכנה main() שמקבלת קלט, מאכלסת מסוג 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(); }
אנחנו צריכים תוכנית בדיקות דומה למחלקה Database.
// 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(); }
שימו לב שתוכניות הבדיקה הפשוטות האלה הן שלב ראשון וטוב, אבל הן דורשות מאיתנו כדי לבדוק ידנית את הפלט ולוודא שהתוכנה פועלת כראוי. בתור מערכת נהיית גדולה יותר, ובדיקה ידנית של הפלט הופכת במהירות ללא מעשית. בשיעור הבא נציג את תוכניות הבדיקה לבדיקה עצמית בטופס של בדיקות יחידה (unit testing).
עיצוב האפליקציה שלנו הושלם. השלב הבא הוא להטמיע את קובצי ה- .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.
זכרו – כל הקוד צריך לעמוד במוסכמות הקידוד שלנו, שחוזרות על עצמן כאן לנוחותך:
- כל תוכנית שאנחנו כותבים מתחילה בתגובת כותרת, הכוללת את השם של המחבר, פרטי הקשר שלו, תיאור קצר והשימוש בו (אם רלוונטי). כל פונקציה/method מתחילים בהערה לגבי פעולה ושימוש.
- אנחנו מוסיפים הערות הסברים שמבוססות על משפטים מלאים, בכל פעם שהקוד לא לתעד את עצמו. לדוגמה, אם העיבוד מסובך, לא מובן מעניין או חשוב.
- השתמשו תמיד בשמות תיאוריים: משתנים הם מילים עם אותיות קטנות, שמופרדות באמצעות _, כמו ב-my_variable. כדי לסמן שמות של פונקציות או שיטות, צריך להשתמש באותיות רישיות מילים, כמו ב-MyExReasonFunction(). קבועים מתחילים באות k וגם להשתמש באותיות רישיות כדי לסמן מילים, למשל kDaysInweek.
- כניסת פיסקה היא בכפולות של שתיים. הרמה הראשונה היא שני רווחים; במקרה של יש צורך בכניסת פסקה, יש לנו ארבעה רווחים, שישה רווחים וכו'.
ברוכים הבאים לעולם האמיתי!
ביחידת הלימוד הזו נציג שני כלים חשובים מאוד המשמשים את רוב הנדסת התוכנה ארגונים. הכלי הראשון הוא כלי build והשני הוא ניהול הגדרות. המערכת. שני הכלים האלה חיוניים בהנדסת תוכנה תעשייתית, הרבה מהנדסים עובדים בדרך כלל על מערכת גדולה אחת. הכלים האלה עוזרים לתאם לבקרה על שינויים בבסיס הקוד ולספק אמצעי יעיל להדר וקישור של מערכת מקובצי תוכנה וכותרות רבים.
קובצי Cookie
תהליך הבנייה של תוכנה מנוהל בדרך כלל באמצעות כלי build, ומקשר את הקבצים הנדרשים, בסדר הנכון. לעיתים קרובות, קובצי C++ של יחסי התלות, לדוגמה, פונקציה שנקראת בתוכנית אחת נמצאת בתוכנית אחרת בתוכנית. לחלופין, ייתכן שיש צורך בקובץ כותרת על ידי כמה קובצי .cpp שונים. א' כלי ה-build קובע את סדר ההידור הנכון מיחסי התלות האלה. היא בנוסף, הם צריכים להדר רק קבצים שהשתנו מאז ה-build האחרון. פעולה זו יכולה לשמור נדרש זמן רב במערכות שכוללות מאות או אלפי קבצים.
לרוב משתמשים בכלי build בקוד פתוח שנקרא Make. למידע נוסף, אפשר לקרוא עד עכשיו מאמר. בודקים אם אתם יכולים ליצור תרשים תלות לאפליקציה Composer Database. ואז מתרגמים את הטקסט הזה לקובץ makefile.כאן את הפתרון שלנו.
מערכות לניהול תצורות
הכלי השני שבו משתמשים בהנדסת תוכנה תעשייתית הוא ניהול תצורה (CM). היא משמשת לניהול השינויים. נניח שיוסי וסוזאן הם כותבי טכנולוגיה ושניהם עובדים על עדכונים במדריך טכני. במהלך הפגישה, שהמנהל מקצה לכל אחד מהם קטע מתוך אותו מסמך לצורך עדכון.
המדריך הטכני מאוחסן במחשב שגם ליוסי וגם לסוזן יש גישה אליו. אם אין כלי או תהליך של CM, עלולות להיווצר מספר בעיות. אחת תרחיש אפשרי הוא שהמחשב שמאחסן את המסמך מוגדר דני ורונית לא יכולים לעבוד על המדריך באותו זמן. הפעולה הזו תאט להוריד אותם באופן משמעותי.
מצב מסוכן יותר מתרחש כשמחשב האחסון מאפשר את המסמך לפתיחה על ידי יוסי וסוזאן בו-זמנית. זה מה שקורה:
- חיים פותח את המסמך במחשב ועובד על החלק שלו.
- שון פותחת את המסמך במחשב ועובדת על החלק שלה.
- יוסי משלים את השינויים ושומר את המסמך במחשב האחסון.
- דנה משלימה את השינויים ושומרת את המסמך במחשב האחסון.
באיור הזה מוצגת בעיה שעלולה לקרות כאשר אין פקדים בעותק היחיד של המדריך הטכני. כשסוזן שומרת את השינויים שלה, היא מחליפה את הטקסטים שנוצרו על ידי יוסי.
זה בדיוק סוג המצב שמערכת CM יכולה לשלוט בו. עם מנהל/ת הקהילה במערכת, גם בוב וגם סוזן "לשלם" עותק שלהם של המודל ולעבוד עליהם. כשיוסי בודק את השינויים בחזרה, המערכת יודעת שסיוון קיבלה עותק משלה בקופה. כשסוזן בודקת את העותק שלה, המערכת מנתח את השינויים שגם בוב וגם סוזן ביצעו, ויוצר גרסה חדשה שממזגת יחד את שתי קבוצות השינויים.
במערכות של התאמה ללקוחות יש כמה תכונות, מעבר לניהול של שינויים בו-זמנית, כפי שמתואר למעלה. מערכות רבות מאחסנות ארכיונים של כל הגרסאות של מסמך, החל הזמן שבו הוא נוצר. אם מדובר במדריך טכני, זה יכול להיות שימושי מאוד למשתמש יש גרסה ישנה של המדריך ושואל את הכותב הטכני שאלות. מערכת CM תאפשר לכותב הטכני לגשת לגרסה הישנה ותוכל כדי לראות מה המשתמש רואה.
מערכות התאמה ללקוחות מועילות במיוחד בשליטה בשינויים שמתבצעים בתוכנה. כאלה נקראות מערכות לניהול הגדרות תוכנה (SCM). אם אתם שוקלים מספר עצום של קובצי קוד מקור בודדים בהנדסת תוכנה גדולה ואת המספר העצום של מהנדסים שחייבים לבצע בהם שינויים, ברור שמערכת SCM היא קריטית.
ניהול תצורת תוכנה
מערכות SCM מבוססות על רעיון פשוט: העותקים המוחלטים של הקבצים נשמרים במאגר מרכזי. אנשים בודקים עותקים של קבצים מהמאגר, לעבוד על העותקים האלה, ולאחר מכן לבדוק אותם שוב אחרי שהם מסיימים. SCM מנהלים ועוקבים אחר גרסאות קודמות של מספר אנשים מול מאסטר יחיד הוגדרה.
כל מערכות SCM מספקות את התכונות החיוניות הבאות:
- ניהול בו-זמניות (concurrency)
- ניהול גרסאות
- סנכרון
נבחן כל אחת מהתכונות האלה לעומק.
ניהול בו-זמניות (concurrency)
המונח 'בו-זמניות' מתייחס לעריכה בו-זמנית של קובץ על ידי יותר מאדם אחד. כשיש מאגר גדול, אנחנו רוצים שאנשים יוכלו לעשות את זה, אבל זה עלול להוביל לבעיות מסוימות.
ניקח דוגמה פשוטה בתחום ההנדסה: נניח שאנחנו מאפשרים למהנדסים כדי לשנות את אותו קובץ בו-זמנית במאגר מרכזי של קוד מקור. Client1 ו-Client2 צריכים לבצע שינויים בקובץ בו-זמנית:
- Client1 פותח את bar.cpp.
- Client2 פותח את bar.cpp.
- Client1 משנה את הקובץ ושומר אותו.
- Client2 משנה את הקובץ ושומר אותו מחליף את השינויים של לקוח1.
כמובן שאנחנו לא רוצים שזה יקרה. גם אם שלטנו במצב באמצעות לאפשר לשני המהנדסים לעבוד על עותקים נפרדים במקום ישירות על מאסטר (כמו באיור שלמטה), יש להתאים את העותקים. נפוצים מערכות 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.
הפקודה לייבא מעתיקה את התוכן של הספרייה 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 ובהעברה של svn במקום פקודות של מערכת ההפעלה. כדי להוסיף קובץ חדש, לוחצים על הוספה של svn וכדי למחוק אותו. יש להשתמש בקובץ svn delete. אם רוצים רק לערוך, פשוט פותחים את עם כלי העריכה שלך, ולא תהיה לך אפשרות לערוך אותו!
יש כמה שמות של ספריות רגילים שנמצאים בשימוש לעיתים קרובות ב-Subversion ה"תא המטען" מאגר מכיל את קו הפיתוח העיקרי של הפרויקט. "ענפים" מאגר מכיל כל גרסת הסתעפות שאתה עובד עליה.
$ 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 המשמעות היא שהתרחש מיזוג. בגרסת המאגר הייתה אבל השינויים לא מתנגשים עם שלך. ג' מציין התנגשות. כלומר, השינויים מהמאגר חופפים לשינויים שלכם, ועכשיו עליכם לבחור ביניהם.
לכל קובץ שיש בו התנגשות, גרסת Subversion מוסיפה שלושה קבצים עותק:
- file.mine: זהו הקובץ שלכם כפי שהוא היה בעותק העבודה לפני עודכן עותק העבודה שלך.
- file.rOLDREV: זהו הקובץ שיצאת מהמאגר לפני ביצוע השינויים שלך.
- file.rNEWREV: קובץ זה הוא הגרסה הנוכחית במאגר.
ניתן לבצע אחת משלוש הפעולות הבאות כדי לפתור את המחלוקת:
- עוברים על הקבצים ומבצעים את המיזוג באופן ידני.
- מעתיקים אחד מהקבצים הזמניים שנוצרו על ידי SVN מעל גרסת העותק הפעיל.
- מריצים את הפקודה svn return כדי לבטל את כל השינויים.
אחרי שתפתרו את המחלוקת, תצטרכו להודיע ל-SVN על ידי הרצת svn נפתרה. פעולה זו מסירה את שלושת הקבצים הזמניים ו-SVN לא מציג יותר את הקובץ מצב של התנגשות.
הדבר האחרון שצריך לעשות הוא לשמור את הגרסה הסופית למאגר. הזה מתבצעת באמצעות הפקודה svn Commission. כשמבצעים שינוי, צריך כדי לספק הודעה ביומן שמתארת את השינויים. מצורפת הודעת היומן הזו לגרסה הקודמת שיצרתם.
svn commit -m "Update files to include new headers."
יש עוד הרבה יותר מידע על SVN, והאופן שבו הוא יכול לתמוך בתוכנות גדולות ופרויקטים הנדסיים. יש משאבים רבים זמינים באינטרנט - רק תחפש ב-Google את המילה Subversion.
לתרגול, אפשר ליצור מאגר עבור מערכת Composer Database, ולייבא כל הקבצים שלכם. לאחר מכן קראו עותק תקין של האפליקציה וביצעו את הפקודות שמתוארות. למעלה.
קובצי עזר
יישום: מחקר באנטומיה
כדאי לצפות בסרטוני eSkeletons של האוניברסיטה מטקסס באוסטין