ข้อมูลเบื้องต้นเกี่ยวกับการเขียนโปรแกรมและ 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.
การสังเกตการณ์ครั้งแรกคือข้อความแนะนำที่แสดง 1 ครั้งต่อโปรแกรม เราต้องการตัวสร้างหมายเลขสุ่มเพื่อกำหนดระยะห่างของศัตรู กลม เราต้องการกลไกสำหรับการรับอินพุตมุมจากโปรแกรมเล่นและ เป็นโครงสร้างแบบวนลูปอย่างเห็นได้ชัด เนื่องจากเล่นซ้ำไปเรื่อยๆ จนกว่าเราจะโจมตีศัตรู นอกจากนี้ เรายัง ต้องการฟังก์ชันสำหรับการคำนวณระยะทางและมุม สุดท้าย เราต้องคอยติดตาม ว่ามันต้องยิงใส่ศัตรูกี่นัด พอๆ กับจำนวนศัตรูที่เรามี ในระหว่างการเรียกใช้โปรแกรม รายละเอียดที่เป็นไปได้ของโปรแกรมหลักมีดังนี้
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;
ขั้นตอนที่ Fire จะจัดการการเล่นเกม ในฟังก์ชันนั้น เราจะเรียก ตัวสร้างหมายเลขสุ่มเพื่อหาระยะห่างของศัตรู จากนั้นตั้งค่าลูปเพื่อ รับข้อมูลอินพุตของผู้เล่นและคำนวณดูว่า พวกเขาโจมตีศัตรูได้ไหม เงื่อนไขการป้องกันบนลูปคือระยะที่เราเข้าใกล้ศัตรูมากแล้ว
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() คุณจะต้องใส่ math.h ด้วย ลอง ที่เขียนโปรแกรมนี้ขึ้น - เป็นแนวทางปฏิบัติที่ดีในการแยกส่วนปัญหา การตรวจสอบ C++ พื้นฐาน อย่าลืมทำงานเพียงอย่างเดียวในแต่ละฟังก์ชัน นี่คือ เป็นโปรแกรมที่ซับซ้อนที่สุดที่เราได้เขียนขึ้นมา ดังนั้น อาจใช้เวลาเล็กน้อย ถึงเวลาดำเนินการแล้วนี่คือวิธีแก้ปัญหา
ตัวอย่างที่ 2: ฝึกฝนด้วยตัวชี้
ข้อควรจำ 4 ข้อเมื่อทำงานกับเคอร์เซอร์มีดังนี้- เคอร์เซอร์คือตัวแปรที่เก็บที่อยู่ของหน่วยความจำ ขณะที่โปรแกรมกำลังดำเนินการ
ตัวแปรทั้งหมดจะจัดเก็บไว้ในหน่วยความจำ โดยแต่ละตัวจะอยู่ในที่อยู่หรือตำแหน่งที่ไม่ซ้ำกันของตัวเอง
ตัวชี้เป็นตัวแปรประเภทพิเศษที่มีที่อยู่หน่วยความจำ
มากกว่าค่าข้อมูล เช่นเดียวกับข้อมูลที่ถูกแก้ไขเมื่อใช้ตัวแปรปกติ
ค่าของที่อยู่ที่จัดเก็บไว้ในตัวชี้จะถูกปรับเปลี่ยนเป็นตัวแปรตัวชี้
ถูกชักจูง เช่น
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 จะชี้ไปที่ Pointee
5.
โปรดสังเกตการใช้ปุ่ม "new" เพื่อจัดสรรหน่วยความจำสำหรับจำนวนเต็มของเรา Pointee นี่คือสิ่งที่เราต้องทำก่อนที่จะพยายามเข้าถึงผู้ให้คะแนน
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++ กำลังทำงานโดยมีเคอร์เซอร์ลืมเริ่มต้น ผู้ชี้ตำแหน่ง บางครั้งปัญหานี้อาจทำให้รันไทม์ขัดข้องเนื่องจากเรากำลังเข้าถึงข้อมูล ตำแหน่งในหน่วยความจำซึ่งมีข้อมูลที่ไม่รู้จัก ถ้าเราพยายามแก้ไข ข้อมูล เราจะอาจก่อให้เกิดความเสียหายกับหน่วยความจำเล็กน้อย และทำให้สามารถตรวจหาข้อบกพร่องที่ได้ยาก
- การกำหนดตัวชี้ระหว่างตัวชี้ 2 ตัวจะทำให้ชี้ไปที่ผู้ชี้ตำแหน่งเดียวกัน
ดังนั้นงาน y = x; ทำให้ y ชี้ไปที่จุดเดียวกับ x การกำหนดเคอร์เซอร์
ไม่สัมผัสผู้ใด เพียงแค่เปลี่ยนตัวชี้ 1 ตัวให้มีตำแหน่งเดียวกัน
เป็นตัวชี้อื่น หลังจากกำหนดเคอร์เซอร์แล้ว ตัวชี้ 2 ตัวจะ "แชร์" เวลา
Pointee
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. จัดสรร 2 ตัวชี้ 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.
โปรดสังเกตในตัวอย่างนี้ว่า เราไม่เคยจัดสรรหน่วยความจำด้วยฟิลด์ "ใหม่" เราได้ประกาศตัวแปรจำนวนเต็มปกติและจัดการตัวแปรผ่านตัวชี้
ในตัวอย่างนี้ เราแสดงการใช้โอเปอเรเตอร์ลบที่ยกเลิกการจัดสรร หน่วยความจำฮีป และวิธีที่เราสามารถจัดสรรให้กับโครงสร้างที่ซับซ้อนยิ่งขึ้น เราจะพูดถึง การจัดระเบียบหน่วยความจำ (ฮีปและสแต็กรันไทม์) ในบทเรียนอื่น ตอนนี้ขอแค่ ให้คิดว่าฮีปเป็นเหมือนพื้นที่จัดเก็บหน่วยความจำที่ว่างไว้ให้โปรแกรมที่กำลังเรียกใช้อยู่
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 ได้โดย กำลังประกาศ Duplicate() เป็น Duplicate(int *x), ซึ่งในกรณีนี้คือ x เป็นตัวชี้ไปยัง int จากนั้นเรียก Duplicate() ที่มีอาร์กิวเมนต์ &x (address-of 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 คุณอาจใช้ฟังก์ชันไลบรารีที่คุณรู้จัก (หรือสูตรทางคณิตศาสตร์) เพื่อช่วยให้โปรแกรมของคุณทำงานได้เร็วขึ้น หรืออาจเป็น ในการเขียนโปรแกรมนี้โดยใช้ for- Loop เพื่อระบุว่าหมายเลขใดสมบูรณ์หรือไม่ หรือผลรวมของอนุกรม (หมายเหตุ: ขึ้นอยู่กับเครื่องและโปรแกรมที่ใช้ อาจใช้เวลาสักพักจึงจะพบหมายเลขนี้)
- แบบฝึกหัด 2
ร้านหนังสือมหาวิทยาลัยต้องการความช่วยเหลือจากคุณในการประมาณธุรกิจสำหรับ ปี ประสบการณ์แสดงให้เห็นว่าการขายจะขึ้นอยู่กับว่าหนังสือนั้นๆ จำเป็นหรือไม่ สำหรับหลักสูตรหรือเป็นเพียงตัวเลือก และมีการใช้ในชั้นเรียนหรือไม่ ก่อนหน้านี้ ตำราเรียนใหม่ที่จำเป็นต้องหนังสือจะขายให้กับ 90% ของผู้ที่มีโอกาสลงทะเบียนเรียน แต่หากเคยใช้ในชั้นเรียนมาก่อน จะมีแค่ 65% ที่จะซื้อ ในทำนองเดียวกัน 40% ของผู้ที่มีโอกาสลงทะเบียนจะซื้อหนังสือเรียนใหม่ (ไม่บังคับ) เคยมีการใช้ในชั้นเรียนมาก่อนเพียง 20% ที่จะซื้อ (โปรดทราบว่า "ใช้แล้ว" ที่นี่ ไม่ได้หมายถึงหนังสือมือสอง)
เขียนโปรแกรมที่ยอมรับเป็นอินพุตชุดหนังสือ (จนกว่าผู้ใช้จะป้อน ผู้เฝ้าสังเกตการณ์) สำหรับหนังสือแต่ละเล่ม จะมีรหัสสำหรับหนังสือ ค่าใช้จ่ายหนึ่งชุดสำหรับ หนังสือ จำนวนหนังสือที่มีอยู่ในปัจจุบัน การลงทะเบียนในชั้นเรียน และข้อมูลที่บอกว่าหนังสือนั้นจำเป็น/ไม่บังคับ เป็นหนังสือใหม่/มือสองแล้ว อาส เพื่อแสดงข้อมูลอินพุตทั้งหมดในหน้าจอที่มีการจัดรูปแบบอย่างสวยงาม ต้องสั่งซื้อหนังสือกี่เล่ม (หากมี โปรดทราบว่าระบบจะสั่งซื้อเฉพาะหนังสือใหม่เท่านั้น) ราคารวมของคำสั่งซื้อแต่ละรายการ
จากนั้นหลังจากที่ป้อนข้อมูลทั้งหมดเรียบร้อยแล้ว ให้แสดงค่าใช้จ่ายรวมของคำสั่งซื้อหนังสือทั้งหมด และ กำไรที่คาดไว้หากร้านค้าจ่าย 80% ของราคาขายปลีกที่แนะนำ เนื่องจากเรายังไม่เคย กล่าวถึงวิธีการต่างๆ ในการจัดการกับข้อมูลขนาดใหญ่ที่เข้ามาในโปรแกรม ( ด้วยตนเอง!) เพียงประมวลผลหนังสือครั้งละหนึ่งเล่ม และแสดงหน้าจอเอาต์พุตสำหรับหนังสือเล่มนั้น จากนั้น เมื่อผู้ใช้ป้อนข้อมูลทั้งหมดเสร็จแล้ว โปรแกรมของคุณจะแสดงผลออกมา มูลค่ารวมและกำไร
ก่อนที่คุณจะเริ่มเขียนโค้ด ให้ลองคิดเกี่ยวกับการออกแบบโปรแกรมนี้ แยกชุดฟังก์ชัน และสร้างฟังก์ชัน main() ที่อ่านเหมือนกับ เค้าโครงสำหรับวิธีแก้ปัญหาของคุณ ตรวจสอบว่าแต่ละฟังก์ชันทำงาน 1 รายการเท่านั้น
ต่อไปนี้คือตัวอย่างเอาต์พุต
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++ ที่มีฟังก์ชันการทำงานเต็มรูปแบบ ซึ่งจะใช้ แอปพลิเคชันฐานข้อมูล
โปรแกรมของเราจะช่วยให้เราจัดการฐานข้อมูลของผู้ประพันธ์เพลงและข้อมูลที่เกี่ยวข้อง เกี่ยวกับพวกเขา ฟีเจอร์ของโปรแกรมมีดังนี้
- ความสามารถในการเพิ่มนักประพันธ์เพลงใหม่
- ความสามารถในการจัดอันดับนักประพันธ์เพลง (เช่น ระบุว่าเราชอบหรือไม่ชอบมากน้อยเพียงใด เพลงของผู้แต่ง)
- ความสามารถในการดูคอมโพสเซอร์ทั้งหมดในฐานข้อมูล
- ความสามารถในการดูนักแต่งเพลงทั้งหมดตามอันดับ
"มี 2 วิธีในการสร้าง การออกแบบซอฟต์แวร์: วิธีหนึ่งคือการทำให้ทุกอย่างเรียบง่ายจนดูเหมือนมี ไม่มีข้อบกพร่อง อีกวิธีหนึ่งคือ ทำให้ซับซ้อน ไม่มีข้อบกพร่องที่เห็นได้ชัด วิธีแรกนั้นยากกว่ามาก" - C.A.R. Hoare
เราหลายคนได้เรียนรู้การออกแบบและเขียนโค้ดโดยใช้ "กระบวนการ" ของเรา คำถามหลักที่เราเริ่มต้นคือ "โปรแกรมต้องทำอะไรบ้าง" พ แยกวิธีแก้ปัญหาเป็นงาน ซึ่งแต่ละข้อจะช่วยแก้ปัญหา ปัญหาที่เกิดขึ้น งานเหล่านี้จะจับคู่กับฟังก์ชันในโปรแกรม ซึ่งเราเรียกว่าตามลำดับ จาก main() หรือจากฟังก์ชันอื่นๆ แนวทางแบบทีละขั้นตอนนี้เหมาะสำหรับ ปัญหาที่เราต้องแก้ไข แต่บ่อยครั้งที่โปรแกรมของเราไม่ได้เป็นเพียงเส้นตรง ลำดับของงานหรือเหตุการณ์
สำหรับแนวทางเชิงวัตถุ (OO) เราเริ่มต้นด้วยคำถาม " โมเดลเป็นโมเดลได้ไหม" แทนที่จะแบ่งโปรแกรมออกเป็นงานต่างๆ ดังที่อธิบายไว้ ข้างต้น เราจะแบ่งโมเดลออกเป็นโมเดลวัตถุทางกายภาพ วัตถุทางกายภาพเหล่านี้มี สถานะที่กำหนดโดยชุดแอตทริบิวต์ และชุดของพฤติกรรมหรือการกระทำที่ ที่สามารถทำได้ การทำงานอาจเปลี่ยนแปลงสถานะของออบเจ็กต์ หรืออาจ เรียกใช้การดำเนินการของออบเจ็กต์อื่นๆ หลักการพื้นฐานคือวัตถุ "รู้" อย่างไร ทำสิ่งต่างๆ ด้วยตัวเอง
ในการออกแบบ OO เราให้คำจำกัดความของวัตถุทางกายภาพในแง่ของคลาสและวัตถุ แอตทริบิวต์ และพฤติกรรมต่างๆ โดยทั่วไปมีออบเจ็กต์จำนวนมากในโปรแกรม OO อยู่แล้ว อย่างไรก็ตาม วัตถุเหล่านี้โดยส่วนใหญ่จะเหมือนกัน ลองพิจารณาวิธีเหล่านี้
ในแผนภาพนี้ เราได้กำหนดออบเจ็กต์ 2 รายการที่เป็นคลาส Apple แต่ละวัตถุจะมีแอตทริบิวต์และการทำงานเหมือนกับคลาส แต่วัตถุ จะระบุแอตทริบิวต์สำหรับประเภทแอปเปิลที่เจาะจง นอกจากนี้ จอแสดงผล การดำเนินการจะแสดงแอตทริบิวต์สำหรับออบเจ็กต์ที่เฉพาะเจาะจง เช่น "เขียว" และ "Sour"
การออกแบบ OO ประกอบด้วยชุดคลาส ข้อมูลที่เชื่อมโยงกับคลาสเหล่านี้ และชุดการดำเนินการที่ชั้นเรียนทำได้ เรายังต้องระบุ วิธีที่ชั้นเรียนต่างๆ โต้ตอบกัน การโต้ตอบนี้ดำเนินการโดยออบเจ็กต์ ของคลาสที่เรียกใช้การดำเนินการของออบเจ็กต์ของคลาสอื่น ตัวอย่างเช่น เรา อาจมีคลาส AppleOutputer ที่แสดงสีและรสชาติของอาร์เรย์ ของออบเจ็กต์ Apple โดยเรียกใช้เมธอด Display() ของออบเจ็กต์ Apple แต่ละรายการ
ขั้นตอนการออกแบบ OO ที่เราทำมีดังนี้
- ระบุคลาสและกำหนดโดยทั่วไปว่าออบเจ็กต์แต่ละคลาสคืออะไร จัดเก็บเป็นข้อมูลและสิ่งที่ออบเจ็กต์สามารถทำได้
- กำหนดองค์ประกอบข้อมูลของแต่ละคลาส
- กำหนดการดำเนินการของแต่ละชั้นเรียนและการกำหนดระดับการดำเนินการของแต่ละชั้นเรียน
โดยใช้การดำเนินการของชั้นเรียนอื่นๆ ที่เกี่ยวข้อง
สำหรับระบบขนาดใหญ่ ขั้นตอนเหล่านี้จะเกิดขึ้นซ้ำๆ ในระดับรายละเอียดที่ต่างกัน
สำหรับระบบฐานข้อมูลคอมโพสเซอร์ เราจำเป็นต้องมีคลาสคอมโพสเซอร์ที่รวมคำสั่งทั้งหมด ข้อมูลที่เราต้องการจัดเก็บไว้ใน นักแต่งเพลงแต่ละราย ออบเจ็กต์ของคลาสนี้สามารถ เพิ่มระดับหรือลดระดับตัวเอง (เปลี่ยนอันดับ) และแสดงแอตทริบิวต์ของส่วนนั้น
และจำเป็นต้องมีคอลเล็กชันของออบเจ็กต์ Composer ด้วย สำหรับกรณีนี้ เรากำหนดคลาสฐานข้อมูล ซึ่งจะจัดการระเบียนแต่ละรายการ ออบเจ็กต์ของคลาสนี้เพิ่มหรือเรียกข้อมูลได้ ออบเจ็กต์คอมโพสเซอร์ และแสดงแต่ละรายการโดยเรียกใช้การดำเนินการแสดงผล ออบเจ็กต์ Composer
สุดท้าย เราต้องมีอินเทอร์เฟซผู้ใช้บางประเภท เพื่อให้การดำเนินการแบบอินเทอร์แอกทีฟ บนฐานข้อมูล นี่คือคลาสตัวยึดตำแหน่ง กล่าวคือ เราไม่ทราบจริงๆ ว่า หน้าตาของอินเทอร์เฟซผู้ใช้ แต่เรารู้ว่าจำเป็นต้องใช้ อาจจะ จะเป็นกราฟิก หรืออาจเป็นข้อความ สำหรับตอนนี้ เรากำหนดตัวยึดตำแหน่งที่ เพื่อให้เรากรอกข้อมูลในภายหลังได้
เมื่อเราได้ระบุคลาสของแอปพลิเคชันฐานข้อมูลนักประพันธ์แล้ว ขั้นตอนถัดไปคือการกำหนดแอตทริบิวต์และการดำเนินการสำหรับชั้นเรียน ในอีก ในการสมัครเข้าร่วมที่ซับซ้อน เราจะนั่งอยู่กับดินสอและกระดาษ หรือ UML หรือบัตร CRC หรือ OOD เพื่อร่างแผนผังลำดับชั้นของคลาสและวิธีที่วัตถุโต้ตอบ
สำหรับฐานข้อมูล Composer ของเรา เรากำหนดคลาส Composer ซึ่งมี ที่เราต้องการจัดเก็บไว้ในผู้แต่งแต่ละคน และยังมีวิธีต่างๆ ในการควบคุม การจัดอันดับสูงสุด และการแสดงข้อมูลเหล่านั้น
คลาสฐานข้อมูลต้องมีโครงสร้างสำหรับเก็บออบเจ็กต์ Composer เราต้องสามารถเพิ่มออบเจ็กต์คอมโพสเซอร์ใหม่ลงในโครงสร้างได้ รวมถึง เรียกออบเจ็กต์ Composer ที่เฉพาะเจาะจง และเราต้องการแสดงวัตถุทั้งหมด ตามลำดับการเข้าร่วมหรือการจัดอันดับ
คลาสอินเทอร์เฟซผู้ใช้ใช้อินเทอร์เฟซที่ขับเคลื่อนด้วยเมนู โดยมีตัวจัดการที่ ในคลาสฐานข้อมูล
หากผู้คนเข้าใจชั้นเรียนได้ง่าย และแอตทริบิวต์และการดำเนินการที่ชัดเจน อย่างในแอปพลิเคชันแต่งเพลง การออกแบบชั้นเรียนนั้นค่อนข้างง่าย แต่ ถ้าคุณมีข้อสงสัยว่าชั้นเรียนต่างๆ เกี่ยวข้องและโต้ตอบกันอย่างไร วิธีที่ดีที่สุดคือการวาดรายละเอียด ก่อนที่จะเริ่มต้น เพื่อเขียนโค้ด
เมื่อเราเข้าใจการออกแบบที่ชัดเจนและได้ประเมินแล้ว (ข้อมูลเพิ่มเติมเกี่ยวกับ ในไม่ช้า) เราจะกำหนดอินเทอร์เฟซ สำหรับแต่ละชั้นเรียน เราไม่กังวลเรื่องการติดตั้งใช้งาน รายละเอียดในจุดนี้ได้ เพียงแต่ว่าแอตทริบิวต์และการดำเนินการคืออะไร และส่วน ของชั้นเรียน สถานะและการดำเนินการที่ใช้ได้สำหรับชั้นเรียนอื่น
โดยปกติใน C++ นั้น เราจะกำหนดไฟล์ส่วนหัวสำหรับแต่ละคลาส นักประพันธ์ class มีสมาชิกข้อมูลส่วนตัวสำหรับข้อมูลทั้งหมดที่เราต้องการจัดเก็บไว้ในนักแต่งเพลง เราต้องการตัวเข้าถึง (เมธอด ("get") และการกลายพันธุ์ (เมธอด "set") รวมถึง การดำเนินการหลักสำหรับชั้นเรียน
// 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 หรือคลาส ไว้ในคลาสฐานข้อมูลเพื่อแสดง บันทึก Composer และเข้าถึงโดยตรงจากที่นั่น แต่นั่นคงเป็น "under-objectification" กล่าวคือ เราไม่ได้สร้างแบบจำลองด้วยวัตถุมากนัก เท่าที่เราจะทำได้
คุณจะเห็นเมื่อเริ่มดำเนินการติดตั้งใช้งาน Composer และฐานข้อมูล การมีคลาส Composer แยกต่างหากนั้นจะดูสะอาดตากว่า โดยเฉพาะอย่างยิ่ง การมีการดำเนินการแบบอะตอมแยกต่างหากในออบเจ็กต์ Composer ทำให้การติดตั้งใช้งานง่ายขึ้นมาก ของเมธอด Display() ในคลาสฐานข้อมูล
แน่นอนว่ายังมีอีกอย่างที่ "การบิดเบือนความจริง" ที่ไหน เราพยายามทำให้ทุกอย่างเป็นชั้นเรียน หรือมีชั้นเรียนมากเกินความจำเป็น ใช้เวลา ฝึกฝนเพื่อหาสมดุลที่เหมาะสม แล้วคุณจะพบว่าโปรแกรมเมอร์แต่ละคน จะมีความคิดเห็นต่างกัน
การพิจารณาว่าคุณปฏิบัติต่อตนเองมากเกินไปหรือน้อยเกินไปมักจะแยกอย่างละเอียดถี่ถ้วน การสร้างแผนภาพชั้นเรียน อย่างที่กล่าวไปก่อนหน้านี้ การจัดชั้นเรียนเป็นสิ่งสําคัญ ออกแบบก่อนเริ่มเขียนโค้ด ซึ่งสามารถช่วยคุณวิเคราะห์แนวทางของคุณได้ ทั่วไป เครื่องหมายที่ใช้สำหรับวัตถุประสงค์นี้คือ 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++ จะต้องเป็นชั้นเรียน อันที่จริง หากการประมวลผลเป็นลำดับ หรือทำงานเป็นหลัก เช่น ในโปรแกรมเมนูนี้ การใช้อย่างเป็นขั้นตอนก็สามารถทำได้ สิ่งสำคัญคือการปรับใช้กลุ่มเป้าหมายในลักษณะที่ยังคงเป็น "ตัวยึดตำแหน่ง" เช่น ถ้าต้องการสร้างอินเทอร์เฟซผู้ใช้แบบกราฟิก ณ จุดใดจุดหนึ่ง เราควร ไม่ต้องเปลี่ยนแปลงอะไรในระบบ ยกเว้นอินเทอร์เฟซผู้ใช้
สิ่งสุดท้ายที่เราต้องทำในการสมัครคือโปรแกรมเพื่อทดสอบชั้นเรียน สำหรับคลาส Composer เราต้องการโปรแกรม main() ที่รับอินพุตและป้อนข้อมูล 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(); }
โปรดทราบว่าโปรแกรมทดสอบง่ายๆ เหล่านี้เป็นก้าวแรกที่ดี แต่เราจำเป็นต้อง เพื่อตรวจสอบผลลัพธ์ด้วยตนเองเพื่อให้แน่ใจว่าโปรแกรมทำงานอย่างถูกต้อง อาส ระบบมีขนาดใหญ่ขึ้น และการตรวจสอบเอาต์พุตด้วยตนเองก็กลายเป็นสิ่งที่ใช้งานไม่ได้อย่างรวดเร็ว ในบทเรียนถัดไป เราจะแนะนำโปรแกรมทดสอบการตรวจสอบตนเองในแบบฟอร์ม ของ 1 หน่วย
ตอนนี้การออกแบบแอปพลิเคชันของเราเสร็จสมบูรณ์แล้ว ขั้นตอนถัดไปคือการนำ ไฟล์ .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
ใช้วิธีการที่คุณกำหนดในคลาสฐานข้อมูลเพื่อนำอินเทอร์เฟซผู้ใช้ไปใช้ ทำให้วิธีการของคุณป้องกันข้อผิดพลาดได้ ตัวอย่างเช่น อันดับควรอยู่ในช่วงอยู่เสมอ 1-10 อย่าให้ใครเพิ่มนักแต่งเพลง 101 คน เว้นแต่คุณจะวางแผนที่จะเปลี่ยน ในคลาสฐานข้อมูล
โปรดทราบว่าโค้ดทั้งหมดต้องเป็นไปตามข้อกำหนดการเขียนโค้ดของเรา ซึ่งจะเกิดขึ้นซ้ำๆ เพื่อความสะดวกของคุณ:
- ทุกโปรแกรมที่เราเขียนจะเริ่มต้นด้วย ความคิดเห็น บนส่วนหัว ซึ่งระบุชื่อของ ผู้เขียน ข้อมูลติดต่อ คำอธิบายสั้นๆ และการใช้งาน (หากเกี่ยวข้อง) ฟังก์ชัน/เมธอดทั้งหมดจะเริ่มด้วยความคิดเห็นเกี่ยวกับการดำเนินการและการใช้งาน
- เราจะเพิ่มความคิดเห็นโดยใช้ประโยคที่สมบูรณ์ ทุกครั้งที่โค้ด ไม่ได้บันทึกตัวเอง เช่น หากการประมวลผลนั้นยุ่งยาก ไม่ชัดเจน หรือมีความสำคัญ
- ใช้ชื่อที่สื่อความหมายเสมอ: ตัวแปรคืออักษรตัวพิมพ์เล็กที่คั่นด้วย _ เช่นเดียวกับใน my_variable ชื่อฟังก์ชัน/วิธีการใช้อักษรตัวพิมพ์ใหญ่เพื่อทำเครื่องหมาย คำ เช่น MyExcitingFunction() ค่าคงที่จะขึ้นต้นด้วย "k" และ ใช้อักษรตัวพิมพ์ใหญ่เพื่อทำเครื่องหมายคำ เช่น kDaysInWeek
- การเยื้องเป็นจำนวนพหุคูณของสอง ระดับแรกคือช่องว่าง 2 ช่อง หากต่อไป ต้องมีการเยื้อง เราใช้เว้นวรรค 4 ช่อง เว้นวรรค 6 ช่อง ฯลฯ
ยินดีต้อนรับสู่โลกแห่งความจริง!
ในโมดูลนี้ เราจะแนะนำเครื่องมือที่สำคัญมาก 2 อย่างซึ่งใช้ในวิศวกรรมซอฟต์แวร์ส่วนใหญ่ องค์กร เครื่องมือแรกคือเครื่องมือสร้าง ส่วนที่สองคือการจัดการการกำหนดค่า ระบบ เครื่องมือทั้งสองนี้มีความสำคัญต่อวิศวกรรมซอฟต์แวร์อุตสาหกรรม วิศวกรหลายคนมักจะทำงานกับระบบขนาดใหญ่ระบบเดียว เครื่องมือเหล่านี้ช่วยประสานงานและ ควบคุมการเปลี่ยนแปลงฐานของโค้ด และให้วิธีการที่มีประสิทธิภาพในการคอมไพล์ รวมทั้งเชื่อมโยงระบบจากไฟล์โปรแกรมและไฟล์ส่วนหัวจำนวนมาก
ไฟล์มา
โดยปกติแล้ว กระบวนการสร้างโปรแกรมจะจัดการด้วยเครื่องมือบิลด์ ซึ่งรวบรวม และลิงก์ไฟล์ที่จำเป็นตามลำดับที่ถูกต้อง บ่อยครั้งที่ไฟล์ C++ มี ตัวอย่างเช่น ทรัพยากร Dependency ที่มีการเรียกใช้ในโปรแกรมหนึ่งอยู่ในอีกโปรแกรมหนึ่ง ของโปรแกรม หรือบางทีไฟล์ .cpp หลายไฟล์อาจจำเป็นต้องใช้ไฟล์ส่วนหัว ต เครื่องมือสร้างค้นพบลำดับคอมไพล์ที่ถูกต้องจากทรัพยากร Dependency เหล่านี้ จะ และคอมไพล์เฉพาะไฟล์ที่มีการเปลี่ยนแปลงตั้งแต่บิลด์ล่าสุดเท่านั้น ซึ่งจะช่วยบันทึก มักใช้เวลานานในระบบที่มีไฟล์หลายร้อยหรือหลายพันไฟล์
เครื่องมือสร้างแบบโอเพนซอร์สชื่อmake มีการใช้งานกันโดยทั่วไป หากต้องการดูข้อมูลเพิ่มเติม ให้อ่าน ผ่านโปรแกรมนี้ article ดูว่าคุณสามารถสร้างกราฟทรัพยากร Dependency สำหรับแอปพลิเคชันฐานข้อมูล Composer ได้หรือไม่ จากนั้นแปลเป็นไฟล์ชนิดนี้นี่คือ โซลูชันของเรา
ระบบจัดการการกำหนดค่า
เครื่องมือที่สองที่ใช้ในวิศวกรรมซอฟต์แวร์อุตสาหกรรมคือการจัดการการกำหนดค่า (CM) หน้านี้ใช้เพื่อจัดการการเปลี่ยนแปลง สมมติว่า Bob และ Susan เป็นนักเขียนด้านเทคโนโลยีทั้งคู่ และทั้งคู่กำลังปรับปรุง คู่มือด้านเทคนิค ในระหว่างการประชุม มอบหมายเนื้อหาแต่ละส่วนของเอกสารเดียวกันให้อัปเดต
คู่มือทางเทคนิคจะจัดเก็บไว้ในคอมพิวเตอร์ที่ทั้ง Bob และ Susan สามารถเข้าถึงได้ หากไม่มีเครื่องมือหรือกระบวนการ CM ใดๆ เลย ก็อาจเกิดปัญหาหลายประการ หนึ่ง สถานการณ์ที่เป็นไปได้คือ อาจมีการติดตั้งคอมพิวเตอร์สำหรับจัดเก็บเอกสารเพื่อให้ บ๊อบและ Susan ไม่สามารถอ่านคู่มือในเวลาเดียวกันได้ การดำเนินการนี้จะช้า ลดลงอย่างเห็นได้ชัด
จะเกิดสถานการณ์ที่อันตรายขึ้นอีกเมื่อพื้นที่เก็บข้อมูลคอมพิวเตอร์อนุญาต ที่ทั้งบ็อบและซูซานเปิดพร้อมกัน สิ่งที่จะเกิดขึ้นมีดังนี้
- ปวิชเปิดเอกสารในคอมพิวเตอร์และทำงานในส่วนของตัวเอง
- สิตาเปิดเอกสารในคอมพิวเตอร์ทำงานในส่วนของเธอ
- อานนท์ทำการเปลี่ยนแปลงเสร็จและบันทึกเอกสารลงในคอมพิวเตอร์จัดเก็บข้อมูล
- สิตาดำเนินการเปลี่ยนแปลงและบันทึกเอกสารลงในคอมพิวเตอร์จัดเก็บข้อมูล
ภาพประกอบนี้แสดงถึงปัญหาที่อาจเกิดขึ้นหากไม่มีการควบคุม บนสำเนาคู่มือทางเทคนิคฉบับเดียว เมื่อ Susan บันทึกการเปลี่ยนแปลง จะเขียนทับรูปภาพที่สร้างโดย Bob
เหตุการณ์ประเภทนี้คือสถานการณ์ที่ระบบ CM ควบคุมได้ เมื่อมี CM ทั้ง Bob และ Susan "เช็คเอาต์" สำเนาทางเทคนิคของตนเอง ด้วยตนเองและนำไปใช้ เมื่อบัญชาตรวจสอบการเปลี่ยนแปลงของเขาอีกครั้ง ระบบจะรู้ว่า ว่า Susan มีสำเนาวิดีโอของตนเองแล้ว เมื่อ Susan ตรวจสอบสำเนาของเธอ วิเคราะห์การเปลี่ยนแปลงที่ทั้งบ็อบและ Susan ทำ และสร้างเวอร์ชันใหม่ที่ จะรวมการเปลี่ยนแปลง 2 ชุดเข้าด้วยกัน
ระบบ CM มีฟีเจอร์มากมายนอกเหนือจากการจัดการการเปลี่ยนแปลงที่เกิดขึ้นพร้อมกันตามที่อธิบายไว้ ที่ด้านบน ระบบจำนวนมากจะจัดเก็บที่เก็บถาวรของเอกสารทุกเวอร์ชัน ตั้งแต่ เวลาที่สร้าง ในกรณีที่ใช้คู่มือทางเทคนิค สิ่งนี้จะเป็นประโยชน์มาก เมื่อผู้ใช้มีคู่มือเวอร์ชันเก่าและถามคำถามผู้เขียนเทคโนโลยี ระบบ CM จะช่วยให้ผู้เขียนด้านเทคโนโลยีเข้าถึงเวอร์ชันเก่าและ เพื่อดูสิ่งที่ผู้ใช้เห็น
ระบบ CM มีประโยชน์อย่างยิ่งในการควบคุมการเปลี่ยนแปลงที่เกิดขึ้นกับซอฟต์แวร์ เช่น เรียกว่าระบบการจัดการการกำหนดค่าซอฟต์แวร์ (SCM) หากคุณอยาก ไฟล์ซอร์สโค้ดแต่ละไฟล์จำนวนมากในวิศวกรรมซอฟต์แวร์ขนาดใหญ่ และวิศวกรจำนวนมากที่ต้องทำการเปลี่ยนแปลง เป็นที่ชัดเจนว่าระบบ SCM มีความสำคัญ
การจัดการการกำหนดค่าซอฟต์แวร์
ระบบ SCM อิงตามแนวคิดง่ายๆ นั่นคือสำเนาที่สมบูรณ์ของไฟล์ของคุณ จะเก็บไว้ในที่เก็บส่วนกลาง ผู้คนจะดูสำเนาของไฟล์จากที่เก็บ ทำงานกับสำเนาเหล่านั้น แล้วกลับมาตรวจสอบเมื่อทำเสร็จ SCM ระบบจะจัดการและติดตามการแก้ไขโดยผู้ใช้หลายคนจากเอกสารต้นฉบับเพียงเครื่องเดียว ตั้งค่า
ระบบ SCM ทั้งหมดมีฟีเจอร์ที่สำคัญดังต่อไปนี้
- การจัดการการเกิดขึ้นพร้อมกัน
- การกำหนดเวอร์ชัน
- การซิงโครไนซ์
เรามาดูรายละเอียดเพิ่มเติมของแต่ละฟีเจอร์กัน
การจัดการการเกิดขึ้นพร้อมกัน
การเกิดขึ้นพร้อมกันหมายถึงการแก้ไขไฟล์พร้อมกันโดยบุคคลมากกว่า 1 คน เมื่อมีพื้นที่เก็บข้อมูลขนาดใหญ่ เราต้องการให้ผู้ใช้สามารถทำสิ่งนี้ได้ แต่ก็อาจทำให้ กับโจทย์บางอย่าง
ลองพิจารณาตัวอย่างง่ายๆ ในโดเมนวิศวกรรม นั่นคือสมมติว่าเราอนุญาตให้วิศวกร แก้ไขไฟล์เดียวกันพร้อมกันได้ในที่เก็บซอร์สโค้ดส่วนกลาง ทั้ง Client1 และ Client2 ต้องแก้ไขไฟล์พร้อมกัน
- Client1 เปิด bar.cpp
- Client2 เปิด bar.cpp
- Client1 เปลี่ยนแปลงไฟล์และบันทึก
- Client2 จะแก้ไขไฟล์และบันทึกไฟล์ที่เขียนทับการเปลี่ยนแปลงของ Client1
แน่นอนว่า เราไม่อยากให้เหตุการณ์นี้เกิดขึ้น แม้เราจะควบคุมสถานการณ์โดย ให้วิศวกร 2 คนทำงานแยกกันแทนที่จะทำงานกับต้นฉบับโดยตรง (ดังภาพประกอบด้านล่าง) สำเนาจะต้องมีการปรับยอดในลักษณะใดกรณีหนึ่ง พบบ่อยที่สุด ระบบ SCM จัดการปัญหานี้โดยอนุญาตให้วิศวกรหลายคนตรวจสอบไฟล์ ออก ("ซิงค์" หรือ "อัปเดต") และทำการเปลี่ยนแปลงตามต้องการ SCM จากนั้นระบบจะเรียกใช้อัลกอริทึมเพื่อผสานการเปลี่ยนแปลงเมื่อมีการตรวจสอบไฟล์อีกครั้ง ("ส่ง" หรือ "คอมมิต") ไปยังที่เก็บ
อัลกอริทึมเหล่านี้สามารถทำได้ง่าย (ขอให้วิศวกรแก้ไขการเปลี่ยนแปลงที่ขัดแย้งกัน) หรือไม่ง่ายนัก (หาวิธีรวมการเปลี่ยนแปลงที่ขัดแย้งกันอย่างชาญฉลาด และสอบถามวิศวกรว่าระบบขัดข้องจริงหรือไม่)
การกำหนดเวอร์ชัน
การกำหนดเวอร์ชันหมายถึงการติดตามการแก้ไขไฟล์ที่ทำให้สามารถ สร้าง (หรือย้อนกลับไปยัง) ไฟล์เวอร์ชันก่อนหน้า คุณสามารถดำเนินการนี้ ด้วยการทำสำเนาของไฟล์ที่เก็บถาวร เมื่อตรวจสอบลงในที่เก็บ หรือด้วยการบันทึกการเปลี่ยนแปลงทั้งหมดในไฟล์ เราสามารถใช้ไฟล์ที่เก็บถาวร หรือเปลี่ยนข้อมูลเพื่อสร้างเวอร์ชันก่อนหน้า ระบบการกำหนดเวอร์ชันยังสามารถ สร้าง รายงานการบันทึก เกี่ยวกับผู้ที่เช็คอินการเปลี่ยนแปลง เวลาที่เช็คอิน และสิ่งใด ความเปลี่ยนแปลงคือ
การซิงโครไนซ์
สำหรับระบบ SCM บางระบบ ไฟล์แต่ละไฟล์จะได้รับการตรวจสอบและออกจากที่เก็บ ระบบที่มีประสิทธิภาพมากขึ้นช่วยให้คุณตรวจสอบไฟล์ได้มากกว่า 1 ไฟล์พร้อมกัน วิศวกร ลองดูสำเนาที่เก็บ (หรือบางส่วน) ของตนเองและผลงาน ในไฟล์ที่ต้องการ จากนั้นผู้ใช้โอนการเปลี่ยนแปลงกลับไปยังที่เก็บหลัก และอัปเดตสำเนาส่วนบุคคลของตนเองเป็นระยะเพื่อให้ไม่พลาดการเปลี่ยนแปลง ที่คนอื่นสร้างไว้ กระบวนการนี้เรียกว่าการซิงค์หรืออัปเดต
การโค่น
Subversion (SVN) เป็นระบบควบคุมเวอร์ชันโอเพนซอร์ส โดยมีทุกอย่าง ฟีเจอร์ที่อธิบายข้างต้น
SVN ปรับใช้วิธีการง่ายๆ เมื่อเกิดข้อขัดแย้ง ความขัดแย้งคือเมื่อ มีวิศวกรมากกว่า 1 คนทำการเปลี่ยนแปลงที่แตกต่างกันในบริเวณเดียวกันของฐานของโค้ด จากนั้นให้ส่งการเปลี่ยนแปลงทั้งคู่ SVN แจ้งเตือนเฉพาะวิศวกรว่ามี ความขัดแย้ง - ขึ้นอยู่กับวิศวกรว่าจะแก้ไขอย่างไร
เราจะใช้ SVN ในหลักสูตรนี้เพื่อช่วยให้คุณคุ้นเคยกับ การจัดการการกำหนดค่า ระบบดังกล่าวใช้กันมากในอุตสาหกรรม
ขั้นตอนแรกคือการติดตั้ง SVN ในระบบ คลิก ที่นี่สำหรับ วิธีทำ ค้นหาระบบปฏิบัติการของคุณและดาวน์โหลดไบนารีที่เหมาะสม
คำศัพท์ SVN บางรายการ
- การแก้ไข: การเปลี่ยนแปลงในไฟล์หรือชุดของไฟล์ การแก้ไขคือ 1 "สแนปชอต" ในโปรเจ็กต์ที่มีการเปลี่ยนแปลงอยู่ตลอดเวลา
- ที่เก็บ: สำเนาหลักที่ SVN จัดเก็บประวัติการแก้ไขแบบเต็มของโปรเจ็กต์ แต่ละโปรเจ็กต์จะมีที่เก็บ 1 รายการ
- สำเนาการทำงาน: สำเนาที่วิศวกรทำการเปลี่ยนแปลงในโปรเจ็กต์ มี สามารถเป็นสำเนาการทำงานจำนวนมากของโปรเจ็กต์หนึ่งๆ ซึ่งแต่ละสำเนาเป็นของวิศวกรแต่ละคน
- ชำระเงิน: หากต้องการขอสำเนาที่ใช้งานได้จากที่เก็บ สำเนาที่ใช้งานได้ เท่ากับสถานะของโปรเจ็กต์เมื่อทำการตรวจสอบ
- คอมมิต: เพื่อส่งการเปลี่ยนแปลงจากสำเนาที่ใช้งานได้ไปยังที่เก็บส่วนกลาง เรียกอีกอย่างว่าการเช็คอินหรือส่งข้อมูล
- อัปเดต: หากต้องการนำผู้อื่น จากที่เก็บลงในสำเนาที่ทำงานของคุณ หรือเพื่อระบุว่าสำเนาการทำงานของคุณมีการเปลี่ยนแปลงใดๆ ที่ไม่ได้รับอนุญาตหรือไม่ นี่คือ เดียวกับการซิงค์ ตามที่อธิบายไว้ข้างต้น ดังนั้นการอัปเดต/ซิงค์จะเป็นการนำสำเนาที่ใช้งานได้ อัปเดตด้วยสำเนาที่เก็บ
- ความขัดแย้ง: สถานการณ์ที่วิศวกรสองคนพยายามทำการเปลี่ยนแปลงสิ่งเดียวกัน ของไฟล์ที่ต้องการ 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.
คำสั่ง นำเข้า จะคัดลอกเนื้อหาของไดเรกทอรีไดเรกทอรีไปยัง ในที่เก็บได้อีกด้วย เราสามารถดูไดเรกทอรีใน ที่เก็บที่มีคำสั่ง 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.
เมื่อคุณมีสำเนาที่ใช้งานได้แล้ว คุณสามารถทำการเปลี่ยนแปลงในไฟล์และไดเรกทอรี จากที่นั่น สำเนาการทำงานของคุณก็เหมือนกับคอลเล็กชันไฟล์และไดเรกทอรีอื่นๆ - คุณสามารถเพิ่มใหม่หรือแก้ไข ย้ายตำแหน่งไปยังที่ต่างๆ หรือแม้แต่ลบ ฉบับสมบูรณ์ทั้งหมด โปรดทราบว่าหากคุณคัดลอกและย้ายไฟล์ในสำเนาที่ใช้งานได้ คุณจำเป็นต้องใช้สำเนา SVG และ ย้าย svn แทน คำสั่งของระบบปฏิบัติการ หากต้องการเพิ่มไฟล์ใหม่ ให้ใช้ svn add แล้วลบ ไฟล์ ให้ใช้ svn delete ถ้าต้องการแก้ไข ก็แค่เปิด ด้วยโปรแกรมแก้ไขของคุณและแก้ไขได้ทันที
มีชื่อไดเรกทอรีมาตรฐานบางชื่อที่มักใช้กับการย่อย "ลำต้น" ไดเรกทอรี เป็นช่องทางหลักของการพัฒนาสำหรับโครงการของคุณ "กิ่งก้าน" ไดเรกทอรี เป็น Branch เวอร์ชันที่คุณอาจใช้งานอยู่
$ svn list file:///usr/local/svn/repos /trunk /branches
สมมติว่าคุณได้ทำการเปลี่ยนแปลงที่จำเป็นกับสำเนาการทำงานและ ที่ต้องการซิงค์กับที่เก็บ ถ้ามีวิศวกรอื่นๆ ทำงานอยู่ ในที่เก็บส่วนนี้ คุณควรอัปเดตสำเนาที่ใช้งานได้ให้เป็นปัจจุบันเสมอ คุณใช้คำสั่งสถานะ SV เพื่อดูการเปลี่ยนแปลงที่มีได้ สร้าง
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 จะวาง 3 ไฟล์ในการทำงาน ข้อความ:
- file.ine: นี่คือไฟล์ของคุณตามที่มีอยู่ในสำเนาการทำงานก่อนที่คุณจะ อัปเดตสำเนาที่ใช้ได้แล้ว
- file.rOLDREV: นี่คือไฟล์ที่คุณเช็คเอาต์จากที่เก็บก่อนหน้านี้ ในการทำการเปลี่ยนแปลง
- file.rNEWREV: ไฟล์นี้เป็นเวอร์ชันปัจจุบันในที่เก็บ
คุณสามารถทำ 1 ใน 3 วิธีต่อไปนี้เพื่อแก้ไขความขัดแย้ง
- ตรวจสอบไฟล์ต่างๆ และผสานด้วยตนเอง
- คัดลอกไฟล์ชั่วคราวที่ SVN สร้างขึ้นไปยังเวอร์ชันสำเนาที่ใช้งานอยู่
- เรียกใช้ svn Return เพื่อลบการเปลี่ยนแปลงทั้งหมด
เมื่อแก้ไขข้อขัดแย้งแล้ว คุณต้องแจ้งให้ SVN ทราบโดยเรียกใช้ svn ที่แก้ไขแล้ว วิธีนี้จะนำไฟล์ชั่วคราวทั้ง 3 ไฟล์ออกและ SVN จะไม่ดูไฟล์ใน สถานะความขัดแย้ง
สิ่งสุดท้ายที่ต้องทำคือการส่งเวอร์ชันสุดท้ายไปยังที่เก็บ ช่วงเวลานี้ จะทําได้ด้วยคำสั่ง svn โปรดดู เมื่อคุณทำการเปลี่ยนแปลง คุณจะต้อง เพื่อระบุข้อความบันทึก ซึ่งอธิบายการเปลี่ยนแปลงของคุณ มีการแนบข้อความบันทึกนี้ กับการแก้ไขที่คุณสร้าง
svn commit -m "Update files to include new headers."
ยังมีอะไรให้เรียนรู้อีกมากมายเกี่ยวกับ SVN และวิธีรองรับซอฟต์แวร์ขนาดใหญ่ ด้านวิศวกรรม มีแหล่งข้อมูลมากมายบนเว็บ - ก็ใช้ Google ค้นหา "Subversion" นะ
สำหรับการฝึก ให้สร้างที่เก็บสำหรับระบบฐานข้อมูลคอมโพสเซอร์ของคุณ แล้วนำเข้า ไฟล์ทั้งหมด จากนั้น ตรวจสอบสำเนาที่กำลังทำงานและปฏิบัติตามคำสั่งที่อธิบายไว้ ที่ด้านบน
ข้อมูลอ้างอิง
การประยุกต์ใช้: การศึกษากายวิภาคศาสตร์
ดู eSkeletons จาก University แห่งเท็กซัสที่ออสติน