3 โครงสร างควบค ม การทำงานแบบวนซ ำ repetition control statement

คำสั่งแต่ละคำสั่งที่ปรากฏในบทก่อนหน้า เป็นคำสั่งที่เกิดการประมวลผลเพียงครั้งเดียวตามลำดับคำสั่งที่ถูกกำหนดโดยผู้เขียนโปรแกรม ในบทนี้ จะกล่าวถึงคำสั่งการวนซ้ำ (iteration) ซึ่งเป็นคำสั่งที่เกิดการประมวลผลให้วนทำงานซ้ำๆ กับคำสั่งหรือชุดคำสั่งในโปรแกรม ภาษา C++ มีคำสั่งการวนซ้ำอยู่ 3 คำสั่ง คือ คำสั่ง while คำสั่ง do…while และคำสั่ง for คำสั่งการวนซ้ำบางครั้งเรียกว่า คำสั่งการวนรอบ หรือ คำสั่งการวนลูป (looping) เนื่องจาก การทำงานที่มีลักษณะวนเป็นรอบๆ นั่นเอง

4.1 คำสั่ง while

รูปแบบของคำสั่ง while คือ

while (condition)  statement ;
โดยที่ condition หมายถึง นิพจน์จำนวนเต็ม (integral expression) และ statement หมายถึง คำสั่งที่ผู้เขียนโปรแกรมต้องการให้เกิดการประมวลผลในแต่ละรอบ โดยที่ เมื่อใดที่นิพจน์มีค่าไม่เป็นศูนย์ (จริง) คำสั่งจะถูกประมวลผลซ้ำไปเรื่อยๆ จนกระทั่งนิพจน์มีค่าเป็นศูนย์ (เท็จ) คำสั่งจะถูกเพิกเฉย และ โปรแกรมจะย้ายการทำงานไปยังคำสั่งถัดไป เช่นเดียวกับคำสั่งเลือกทำ เงื่อนไขของคำสั่ง while ต้องเขียนอยู่ในเครื่องหมายวงเล็บ ผังงานการทำงานของคำสั่ง while แสดงดังรูป 4-1

รูป 4-1 ผังงานคำสั่ง while

ตัวอย่างที่ 4.1 การใช้ while ลูปในการคำนวณหาผลบวกของจำนวนเต็ม 1 ถึง n

int main() { int n, i=1; cout << "Enter a positive integer: "; cin >> n; long sum=0; while (i<=n)

sum += i++;
cout << "The sum of the first " << n << " integers is "
 << sum;
}

ทดสอบโปรแกรม โดยป้อนค่า n = 8

Enter a positive integer: 8 The sum of the first 8 integers is 36

โปรแกรมนี้จะคำนวณหาผลบวกของ 1+2+3+…+n สำหรับจำนวนเต็ม n ที่ผู้ใช้ป้อนเข้ามา โดยโปรแกรมใช้ตัวแปรท้องถิ่น 3 ตัว คือ n, i และ sum ในแต่ละครั้งที่ลูป while ทำงานซ้ำ ค่าของ i จะถูกนำไปบวกเข้ากับค่า sum แล้วเพิ่มค่าไป i ไป 1 การวนรอบจะหยุดเมื่อ i มีค่ามากกว่า n ดังนั้น n จะเป็นค่าสุดท้ายที่บวกเข้ากับค่า sum ได้ 1+2+3+4+5+6+7+8 = 36

ทดสอบโปรแกรม โดยป้อนค่า n = 100

Enter a positive integer: 100 The sum of the first 8 integers is 5050

ในการประมวลผลครั้งที่สอง ผู้ใช้ได้ป้อน 100 ให้กับ n ทำให้การทำงานของลูป while ต้องทำซ้ำทั้งหมด 100 รอบ เพื่อที่จะคำนวณค่าผลบวกของ 1+2+3+…+98+99+100 = 5050 เป็นค่าของ sum โปรดสังเกตว่า ในโปรแกรมมีการย่อหน้าสำหรับคำสั่งที่อยู่ภายในลูป ซึ่งการกระทำแบบนี้จะทำให้อ่านโปรแกรมได้ง่ายขึ้น โดยเฉพาะโปรแกรมที่มีขนาดใหญ่

ตัวอย่างที่ 4.2 การใช้ลูป while เพื่อคำนวณหาผลบวกของส่วนกลับ (reciprocal)

int main() { int bound; cout << "Enter a positive integer: "; cin >> bound; double sum = 0.0; int i = 0; while ( sum < bound )

sum += 1.0/++i;
cout << "The sum of the first " <<i << " reciprocals is "
 << sum << endl;
}

ทดสอบโปรแกรม โดยป้อนค่า bound = 3

Enter a positive integer: 3 The sum of the first 11 reciprocals is 3.01988

โปรแกรมนี้จะคำนวณหาผลบวกของส่วนกลับ $1+1/2+1/3+…+1/n$ เก็บในตัวแปร sum โดยจะหาค่า n mujเป็นจำนวนเต็มที่มีค่าน้อยที่สุดสำหรับ sum < bound โดยค่าของ bound เป็นค่าที่ผู้ใช้ป้อนเข้ามา

ตัวอย่างที่ 4.3 การใช้ลูป while เพื่อทำให้เกิดการคำนวณแบบวนซ้ำ

int main() { double x; cout << "Enter a positive number: "; cin >> x; while ( x > 0 ) { cout << "sqrt(" << x << ") = " << sqrt(x) << endl;

cout << "Enter another positive number (or 0 to quit): ";
cin >> x;
}
}

ทดสอบโปรแกรม โดยป้อนค่า x = 49, 3.14159, 100000 และ 0 ตามลำดับ

Enter a positive number: 49 sqrt(49) = 7 Enter another positive number (or 0 to quit): 3.14159 sqrt(3.14159) = 1.77245 Enter another positive number (or 0 to quit): 100000 sqrt(100000) = 316.228 Enter another positive number (or 0 to quit): 0

โปรแกรมนี้จะพิมพ์ค่ารากที่สอง (square root) ของตัวเลขแต่ละตัวที่ผู้ใช้ป้อนเข้ามา การใช้ลูป while จะทำให้การประมวลผลโปรแกรมสามารถมีจำนวนการทำงานวนซ้ำเป็นจำนวนกี่รอบได้ ขึ้นกับเงื่อนไข (x>0) ตัวแปร x เป็นตัวแปรควบคุมการทำงานของลูป โดยที่ตัวแปร x จะมีการเปลี่ยนแปลงค่า โดยรับค่า x ตัวใหม่จากผู้ใช้เป็นผู้ป้อน เราเรียกตัวแปรในลักษณะนี้ว่า ตัวแปรควบคุมลูป (loop control variable)

4.2 การหยุดการวนซ้ำ

ในบทที่แล้ว เราเห็นวิธีการใช้คำสั่ง break ในการออกจากคำสั่ง switch (จากตัวอย่างที่ 3.16) ในทำนองเดียวกัน ผู้เขียนโปรแกรมสามารถนำคำสั่ง break นี้มาใช้ควบคุมการวนลูปได้

ตัวอย่างที่ 4.4 การใช้คำสั่ง break เพื่อที่จะหยุดลูป

int main() { int n, i = 1; cout << "Enter a positive integer: "; cin >> n; long sum = 0; while (true) { if (i>n) break; //terminates the loop immediately

sum += i++;
} cout << "The sum of the first" << n << " integers is " << sum; }

ทดสอบโปรแกรม โดยป้อนค่า n = 100

Enter a positive integer: 100 The sum of the first 100 integers is 5050

โปรแกรมนี้มีผลการทำงานเหมือนกับตัวอย่างที่ 4.1 ค่าของตัวแปร i เมื่อเท่ากับค่า n การทำงานของลูปจะหยุด แล้วไปทำงานในส่วนของคำสั่งเพื่อแสดงผลทางหน้าจอในส่วนท้ายของโปรแกรม ในโปรแกรมตัวอย่าง เงื่อนไขที่ใช้ควบคุมลูป while จะเป็นจริง (true) เสมอ ซึ่งหมายความว่าการวนลูปนี้จะวนไปเรื่อยๆ วิธีเขียนคำสั่งในลักษณะนี้เป็นวิธีมาตรฐานในการเขียนโปรแกรมควบคุมลูป while โดยที่ การควบคุมลูปเกิดขึ้นภายในลูป ข้อดีอัสำหรับการใช้คำสั่ง break ภายในลูป คือ จะทำให้ลูปหยุดการทำงานได้ทันที โดยไม่จำเป็นต้องประมวลผลคำสั่งต่างๆ ที่เหลืออยู่ในลูปนั้น

ตัวอย่างที่ 4.5 ตัวเลขฟิโบนาชชิ (Fibonacci)

ตัวเลขฟิโบนาชชิ $F_0,F_1,F_2,F_3,…$ ถูกนิยาม โดยใช้หลักของการแทนค่าไปเรื่อยๆ ตามสมการต่อไปนี้ $$ F_0 = 0 $$ $$ F_1 = 1 $$ $$ F_n = F_{n-1}+F_{n-2} $$

เช่น แทนค่า n ด้วย 2 ในสมการที่สาม จะได้ $$F_2 = F_{2-1} + F_{2-2} = F_1 + F_0 = 1 + 0 = 1$$ ในทำนองเดียวกัน แทนค่า n ด้วย 3 จะได้ $$F_3 = F_{3-1} + F_{3-2} = F_2 + F_1 = 1 + 1 = 2$$ และ แทนค่า n ด้วย 4 จะได้ $$F_4 = F_{4-1} + F_{4-2} = F_3 + F_2 = 2 + 1 = 3$$

ดังนั้น ลำดับของตัวเลขฟิโบนาชชิ 10 ตัวแรก มึค่าเป็น 0, 1, 1, 2, 3, 5, 8, 13, 21 และ 35

int main() { int n, i=1; cout << "Enter a positive integer: "; cin >> n; long sum=0; while (i<=n)

sum += i++;
cout << "The sum of the first " << n << " integers is "
 << sum;
}

0

ทดสอบโปรแกรม โดยป้อนค่า bound = 1000

int main() { int n, i=1; cout << "Enter a positive integer: "; cin >> n; long sum=0; while (i<=n)

sum += i++;
cout << "The sum of the first " << n << " integers is "
 << sum;
}

1

โปรแกรมนี้จะพิมพ์ค่าตัวเลขฟิโบนาชชิทั้งหมด ที่น้อยกว่าค่าของ bound ที่ผู้ใช้ป้อนเข้าไปคำสั่งลูป while นี้ ประกอบด้วย 5 คำสั่งภายในลูป เมื่อเงื่อนไข (f2>bound) มีค่าเป็นจริง คำสั่ง break จะถูกประมวลผลทำให้การวนลูปสิ้นสุดทันที โดยไม่ไปประมวลผล 3 คำสั่งสุดท้ายในรอบนั้น

หมายเหตุ การใช้อักขระขึ้นบรรทัดใหม่ ‘\n’ ในข้อความ “:\n0, 1” ซึ่งจะเป็นการพิมพ์เครื่องหมาย : แล้วขึ้นบรรทัดใหม่ เพื่อพิมพ์ 0, 1 ที่ส่วนต้นของบรรทัด

ตัวอย่างที่ 4.6 การใช้ฟังก์ชั่น exit(0)

int main() { int n, i=1; cout << "Enter a positive integer: "; cin >> n; long sum=0; while (i<=n)

sum += i++;
cout << "The sum of the first " << n << " integers is "
 << sum;
}

2

ทดสอบโปรแกรม โดยป้อนค่า bound = 1000

int main() { int n, i=1; cout << "Enter a positive integer: "; cin >> n; long sum=0; while (i<=n)

sum += i++;
cout << "The sum of the first " << n << " integers is "
 << sum;
}

1

ฟังก์ชั่น exit ( ) เป็นอีกวิธีหนึ่งที่ใช้ในการหยุดการทำงานของลูป เมื่อฟังก์ชั่นนี้ถูกประมวลผล มันจะหยุดการทำงานของทั้งโปรแกรม เนื่องจากโปรแกรมนี้ไม่มีคำสั่งใดๆต่อท้ายลูป ทำให้การหยุดการทำงานของลูปมีผลเหมือนกับการหยุดการทำงานของทั้งโปรแกรม ทำให้ผลที่ได้จากการประมวลผลโปรแกรมนี้เหมือนกับในตัวอย่างที่ 4.5 แม้ว่า โปรแกรมในตัวอย่างนี้แสดงให้เห็นถึงอีกวิธีหนึ่งที่ใช้ในการออกจากลูปที่ไม่รู้จบ แต่วิธีที่นิยมใช้ในการหยุดลูป คือ การใช้คำสั่ง break ดังแสดงในตัวอย่าง 4.5

ตัวอย่างที่ 4.7 การยกเลิกลูปที่ไม่รู้จบ (infinite loop)

int main() { int n, i=1; cout << "Enter a positive integer: "; cin >> n; long sum=0; while (i<=n)

sum += i++;
cout << "The sum of the first " << n << " integers is "
 << sum;
}

4

ทดสอบโปรแกรม โดยป้อนค่า bound = 1000

int main() { int n, i=1; cout << "Enter a positive integer: "; cin >> n; long sum=0; while (i<=n)

sum += i++;
cout << "The sum of the first " << n << " integers is "
 << sum;
}

5

เมื่อปราศจากกลไกในการหยุดการทำงาน ลูปจะถูกประมวลผลไปเรื่อยๆ ไม่รู้จบ การยกเลิกการทำงานของลูปหลังจากที่ได้เริ่มการทำงานไปแล้ว คือการกด <Ctrl>+c (โดยกดปุ่ม Ctrl ค้างแล้วกดปุ่ม c)

4.3 คำสั่ง do…while

รูปแบบของคำสั่ง do…while คือ

int main() { int n, i=1; cout << "Enter a positive integer: "; cin >> n; long sum=0; while (i<=n)

sum += i++;
cout << "The sum of the first " << n << " integers is "
 << sum;
}

6

โดยที่ condition หมายถึง นิพจน์จำนวนเต็ม และ statement หมายถึง คำสั่งที่ผู้เขียนโปรแกรมต้องการให้เกิดการประมวลผลในแต่ละรอบ โดยที่ คำสั่งจะถูกประมวลผล แล้วตรวจสอบว่า นิพจน์มีค่าไม่เป็นศูนย์ (จริง) ก็จะกลับไปประมวลผลคำสั่งซ้ำไปเรื่อยๆ จนกระทั่งนิพจน์มีค่าเป็นศูนย์ (เท็จ) คำสั่งจะถูกเพิกเฉย และ โปรแกรมจะย้ายการทำงานไปยังคำสั่งถัดไป ผังงานการทำงานของคำสั่ง do..while แสดงดังรูป 4-2

รูป 4-2 ผังงานคำสั่ง do..while

คำสั่ง do…while ทำงานเหมือนกับคำสั่ง while ยกเว้นการตรวจสอบเงื่อนไข คำสั่ง do..while จะทำในช่วงท้ายของแต่ละรอบ ในขณะที่คำสั่ง while จะทำก่อนเข้าลูป ดังนั้น การทำงานของคำสั่ง do…while จะทำอย่างน้อย 1 รอบเสมอ ไม่ว่าเงื่อนไขควบคุมจะเป็นเช่นไร ผู้เขียนโปรแกรมสามารถนิยามตัวแปรควบคุมภายในลูปได้

ตัวอย่างที่ 4.8 การใช้คำสั่ง do…while ลูปเพื่อคำนวณหาผลรวมของจำนวนเต็ม 1 ถึง n

int main() { int n, i=1; cout << "Enter a positive integer: "; cin >> n; long sum=0; while (i<=n)

sum += i++;
cout << "The sum of the first " << n << " integers is "
 << sum;
}

7

ทดสอบโปรแกรม โดยป้อนค่า n = 8

Enter a positive integer: 8 The sum of the first 8 integers is 36

โปรแกรมนี้มีผลการทำงานเหมือนกับตัวอย่างที่ 4.1

ตัวอย่างที่ 4.9 ตัวเลขแฟคทอเรียล (factorial)

ตัวเลขแฟคทอเรียล 0!,1!,2!,3!,…ถูกนิยามโดยใช้หลักของการแทนค่าอย่างต่อเนื่องซ้ำๆ ตามสมการ

int main() { int n, i=1; cout << "Enter a positive integer: "; cin >> n; long sum=0; while (i<=n)

sum += i++;
cout << "The sum of the first " << n << " integers is "
 << sum;
}

9

เช่น ให้ n เท่ากับ 1 ในสมการที่สอง จะได้

Enter a positive integer: 8 The sum of the first 8 integers is 36

0

ในทำนองเดียวกัน ถ้าให้ n เท่ากับ 2 จะได้

Enter a positive integer: 8 The sum of the first 8 integers is 36

1

และ ถ้าให้ n เท่ากับ 3 จะได้

Enter a positive integer: 8 The sum of the first 8 integers is 36

2

จากสมการ จะได้ตัวเลขแฟคทอเรียล 7 ตัวแรก เป็น 1, 1, 2, 6, 24, 120 และ 720

Enter a positive integer: 8 The sum of the first 8 integers is 36

3

ทดสอบโปรแกรม โดยป้อนค่า bound = 1000000

Enter a positive integer: 8 The sum of the first 8 integers is 36

4

โปรแกรมนี้จะพิมพ์ค่าตัวเลขแฟคทอเรียลทุกตัวที่มีค่าน้อยกว่าค่าของ bound ซึ่งเป็นค่าที่ผู้ใช้เป็นผู้กำหนด โดยคำสั่ง do…while ในโปรแกรมจะทำงานวนไปเรื่อย ๆ จนกระทั่งเงื่อนไขควบคุม (f < bound) มีค่าเป็นเท็จ

4.4 คำสั่ง for

รูปแบบของคำสั่ง for คือ

Enter a positive integer: 8 The sum of the first 8 integers is 36

5

โดยที่ initialization, condition และ update เป็นนิพจน์ที่อาจมีหรือไม่มีในคำสั่งก็ได้ statement คือ คำสั่งที่ผู้เขียนโปรแกรมต้องการให้เกิดการประมวลผลในแต่ละรอบ ส่วน (initialization ; condition ; update) เป็นส่วนควบคุมการวนรอบ โดยที่ initialization เป็นนิพจน์กำหนดค่าเริ่มต้นให้กับตัวแปรควบคุมของลูปนิพจน์นี้จะถูกประมวลผลก่อนการวนรอบจะเกิดขึ้น condition เป็นนิพจน์เงื่อนไขใช้เพื่อตรวจสอบว่าจะมีการทำงานซ้ำอีกหรือไม่ ในส่วนนี้จะถูกประมวลผลหลังจากการตั้งค่าเริ่มต้นและในทุก ๆ รอบใหม่ของลูป โดยถ้านิพจน์มีค่าไม่เป็นศูนย์ (จริง) คำสั่งต่าง ๆ ภายในลูปจะถูกประมวลผล จนกระทั่งนิพจน์มีค่าเป็นศูนย์ (เท็จ) คำสั่งจะถูกเพิกเฉย และ โปรแกรมจะย้ายการทำงานไปยังคำสั่งถัดไป ส่วน update เป็นนิพจน์ที่กระทำเพื่อปรับเปลี่ยนค่าให้กับตัวแปรควบคุม โดยในส่วนนี้จะถูกประมวลผลทุกครั้งหลังจากที่คำสั่งต่าง ๆ ภายในลูปได้ถูกประมวลผลเสร็จสิ้นในแต่ละรอบแล้ว ลำดับการวนซ้ำที่เกิดขึ้นในคำสั่ง for สรุปได้ ดังนี้

  1. ประมวลผลนิพจน์ initialization
  2. ถ้า condition มีค่าเป็นเท็จ ให้หยุดการทำงานของ loop
  3. ประมวลผลคำสั่งต่าง ๆ ภายใน loop
  4. ประมวลผลนิพจน์ update
  5. ทำซ้ำในขั้นตอนที่ 2 – 4

ผังงานการทำงานของคำสั่ง for แสดงดังรูป 4-3

รูป 4-3 ผังงานคำสั่ง for

ตัวอย่างที่ 4.10 การใช้ for ลูป เพื่อคำนวณหาผลรวมของจำนวนเต็ม 1 ถึง n

Enter a positive integer: 8 The sum of the first 8 integers is 36

6

ทดสอบโปรแกรม โดยป้อนค่า n = 8

Enter a positive integer: 8 The sum of the first 8 integers is 36

7

โปรแกรมนี้มีผลการทำงานเหมือนกับตัวอย่างที่ 4.1 ในที่นี้นิพจน์การตั้งค่าเริ่มต้น คือ int i = 1 นิพจน์เงื่อนไข คือ i ⇐ n และ นิพจน์การปรับเปลี่ยนค่า คือ i++ ให้สังเกตว่า นิพจน์เหล่านี้มีอยู่ในโปรแกรมตัวอย่างที่ 4.1 ตัวอย่างที่ 4.4 และ ตัวอย่างที่ 4.8 เช่นกัน

ในภาษา C++ มาตรฐาน เมื่อตัวแปรควบคุมลูปถูกนิยามภายในคำสั่ง for (ในตัวอย่างนี้ คือ ตัวแปร i) ขอบเขตของตัวแปรนี้จะถูกจำกัดให้ใช้งานได้เฉพาะภายในลูป for เท่านั้น กล่าวคือ ผู้เขียนโปรแกรมไม่สามารถใช้งานตัวแปร i ภายนอกลูป for ถ้ามีตัวแปรที่มีชื่อเหมือนกันประกาศอยู่ภายนอกคำสั่ง for จะถือว่า ตัวแปรนั้นเป็นคนละตัวกับตัวแปรที่ประกาศภายในคำสั่ง for

ตัวอย่างที่ 4.11 การนำชื่อตัวแปรควบคุมลูป for กลับมาใช้อีก

Enter a positive integer: 8 The sum of the first 8 integers is 36

8

ทดสอบโปรแกรม โดยป้อนค่า n = 8

Enter a positive integer: 8 The sum of the first 8 integers is 36

โปรแกรมนี้มีผลการทำงานเหมือนกับตัวอย่างที่ 4.1 คำสั่ง for ทั้งสองในโปรแกรมนี้คำนวณสิ่งเดียวกันกับที่คำสั่ง for ในโปรแกรมในตัวอย่างที่ 4.10 เพียงแต่มีการแบ่งการทำงานออกเป็น 2 ส่วน คือ ทำการหาผลรวมของค่า 1 ถึง n / 2 -1 ในลูปแรก และ สะสมเพิ่มจากค่า n/ 2 ถึง n ในลูปที่สอง โดยที่ คำสั่ง for ทั้งสองมีการนิยามตัวแปรควบคุมเพื่อใช้งานชื่อเหมือนกัน คือ i โดยที่ ตัวแปร i ทั้งสองตัวนี้ถือว่าเป็นตัวแปรคนละตัว เนื่องจาก ตัวแปรทั้งสองถูกประกาศอยู่คนละส่วนในโปรแกรม

ตัวอย่างที่ 4.12 ตัวเลขแฟคทอเรียลอีกครั้ง

Enter a positive integer: 100 The sum of the first 8 integers is 5050

0

ทดสอบโปรแกรม โดยป้อนค่า bound = 1000000

Enter a positive integer: 100 The sum of the first 8 integers is 5050

1

โปรแกรมนี้มีผลการทำงานเหมือนกับตัวอย่างที่ 4.9 โปรแกรมคำสั่ง for นี้มีผลการทำงานเหมือนกับโปรแกรมคำสั่ง do…while เนื่องจาก มีการประมวลผลคำสั่งต่างๆ ที่เหมือนกัน หลังจากที่กำหนดค่าเริ่มต้นของ f เป็น 1 ทั้งสองโปรแกรมได้กำหนดค่าให้ i เป็น 2 แล้วประมวลผลทั้ง 5 คำสั่งวนไปเรื่อยๆ คือ พิมพ์ค่า f, คูณ f ด้วย i , เพิ่มค่า i, ตรวจสอบเงื่อนไข (f ⇐bound) และ หยุดการทำงานของลูปเมื่อเงื่อนไขเป็นเท็จคำสั่ง for เป็นคำสั่งที่มีความยืดหยุ่นในการเขียน เมื่อเปรียบเทียบกับคำสั่ง while และ คำสั่ง do..while ซึ่งจะแสดงในตัวอย่างต่อไป

ตัวอย่างที่ 4.13 การใช้คำสั่ง for แบบนับถอยหลัง

Enter a positive integer: 100 The sum of the first 8 integers is 5050

2

ทดสอบโปรแกรม

Enter a positive integer: 100 The sum of the first 8 integers is 5050

3

โปรแกรมนี้จะพิมพ์จำนวนเต็ม 10 ค่าแรกในลำดับที่กลับกัน

ตัวอย่างที่ 4.14 การใช้คำสั่ง for แบบที่ขนาดของขั้นการนับมีค่ามากกว่าหนึ่ง

Enter a positive integer: 100 The sum of the first 8 integers is 5050

4

ทดสอบโปรแกรม โดยป้อนค่า n = 101

Enter a positive integer: 100 The sum of the first 8 integers is 5050

5

ทดสอบโปรแกรม โดยป้อนค่า n = 975313579

Enter a positive integer: 100 The sum of the first 8 integers is 5050

6

โปรแกรมนี้จะหาว่าตัวเลขที่ผู้ใช้ป้อนเข้ามาเป็นจำนวนเฉพาะหรือไม่ ให้สังเกตว่าคำสั่ง for มีการเพิ่มขึ้นของตัวแปรควบคุม d ขึ้นครั้งละ 2 ในแต่ละรอบ

ตัวอย่างที่ 4.15 การใช้ sentinel เพื่อควบคุมคำสั่ง for

Enter a positive integer: 100 The sum of the first 8 integers is 5050

7

ทดสอบโปรแกรม โดยป้อนค่า n เป็น 44, 77, 55, 22, 99, 33, 11, 66, 88 และ 0 ตามลำดับ

Enter a positive integer: 100 The sum of the first 8 integers is 5050

8

โปรแกรมนี้จะหาค่าที่มากที่สุดของชุดจำนวนเต็มที่ผู้ใช้ป้อนเข้ามา คำสั่ง for จะควบคุมค่าของตัวแปรควบคุม n ตามค่าที่ผู้ใช้ป้อนเข้ามา ซึ่งการวนรอบจะกระทำไปเรื่อย ๆ จนกระทั่งค่า n มีค่าน้อยกว่าหรือเท่ากันกับ 0 การควบคุมการวนรอบตามค่าที่ผู้ใช้ป้อนเข้ามา เรียกว่า เซนทิเนล (sentinel)

ให้สังเกตว่า กลไกควบคุมการวนรอบ (max = n ; n> o;) ภายในคำสั่ง for ไม่มีส่วนของการปรับเปลี่ยน และ การตั้งค่าเริ่มต้น max = n ไม่มีส่วนที่ใช้ในการประกาศตัวแปร ทั้งนี้เนื่องจาก ตัวแปร max ได้ถูกประกาศไว้ก่อนคำสั่ง for ซึ่งทำให้สามารถใช้งานตัวแปรนี้ภายนอกลูปได้ เช่นในส่วนของการแสดงผลที่ท้ายโปรแกรม

ตัวอย่างที่ 4.16 การใช้ loop invariant เพื่อพิสูจน์ว่า for ลูป ถูกต้อง

Enter a positive integer: 100 The sum of the first 8 integers is 5050

9

ทดสอบโปรแกรม โดยป้อนค่า n เป็น 44, 77, 55, 22, 99, 33, 11, 66, 88 และ 0 ตามลำดับ

int main() { int bound; cout << "Enter a positive integer: "; cin >> bound; double sum = 0.0; int i = 0; while ( sum < bound )

sum += 1.0/++i;
cout << "The sum of the first " <<i << " reciprocals is "
 << sum << endl;
}

0

โปรแกรมนี้จะหาค่าที่น้อยที่สุดของชุดจำนวนเต็มบวกที่ผู้ใช้ป้อนเข้าไป ในทำนองเดียวกับโปรแกรมในตัวอย่างที่ 4.15 บรรทัดหมายเหตุ (comment) ที่ในคำสั่ง for แสดงตำแหน่งของ loop invariant ในคำสั่ง for โดย loop invariant เป็นการพิสูจน์ให้เห็นคุณสมบัติ 2 ประการ คือ (1) เงื่อนไขเป็นจริง ณ ตรงนั้นสำหรับทุกๆ การวนรอบของลูป (2) ความจริงที่ว่าเงื่อนไขเป็นจริงเมื่อลูปถูกระงับ พิสูจน์ให้เห็นว่าลูปทำงานถูกต้องสมบูรณ์แบบ ในกรณีนี้ เงื่อนไขที่ว่า min มีค่าน้อยกว่าหรือเท่ากับ n สำหรับทุกค่า n จะเป็นจริงเสมอ เนื่องจากคำสั่ง if จะทำการปรับปรุงค่าของ min ใหม่ ถ้าพบว่าค่าของ n ที่ป้อนเข้ามาตัวสุดท้ายมีค่าน้อยกว่าค่าของ min ที่มีอยู่เดิม และ เงื่อนไขที่ว่า min จะมีค่าเท่ากับค่าใดค่าหนึ่งของ n ก็จะเป็นจริงเสมอ เนื่องจาก min ได้ถูกตั้งค่าเริ่มต้นเป็นค่า n ตัวแรก และ จะถูกแทนที่(ในบางครั้ง)ด้วยค่า n ตัวใหม่และ สุดท้าย ความจริงที่ว่าเงื่อนไขจะเป็นจริงเมื่อลูปถูกระงับ หมายความว่า min จะเป็นค่าที่น้อยที่สุดของจำนวนเต็มบวกที่ป้อนเข้ามา และ ผลที่ได้นั้นก็คือจุดประสงค์ของคำสั่ง for นี้

ตัวอย่างที่ 4.17 จำนวนตัวแปรควบคุมที่มีมากกว่าหนึ่งใน for ลูป

int main() { int bound; cout << "Enter a positive integer: "; cin >> bound; double sum = 0.0; int i = 0; while ( sum < bound )

sum += 1.0/++i;
cout << "The sum of the first " <<i << " reciprocals is "
 << sum << endl;
}

1

ทดสอบโปรแกรม

int main() { int bound; cout << "Enter a positive integer: "; cin >> bound; double sum = 0.0; int i = 0; while ( sum < bound )

sum += 1.0/++i;
cout << "The sum of the first " <<i << " reciprocals is "
 << sum << endl;
}

2

คำสั่ง for ในโปรแกรมนี้มีการกำหนดตัวแปรควบคุม 2 ตัว โดยตัวแปรควบคุมทั้งสองตัว คือ m และ n ถูกประกาศ และ ตั้งค่าเริ่มต้นในกลไกของการควบคุมของคำสั่ง for หลังจากนั้นค่า m จะถูกลดลงทีละ 3 ในขณะที่ค่า n จะถูกเพิ่มขึ้นทีละ 1 ในแต่ละรอบ ทำให้เกิดคู่ลำดับของ (m,n) เป็น (95,11) , (92,12) , (89,13) , (86,14) , (83,15) , (80,16) โดยที่ลูปจะถูกระงับด้วยคู่ของ (80,16) เนื่องจาก 80 หาร 16 ลงตัว

ตัวอย่างที่ 4.18 คำสั่ง for ซ้อน

int main() { int bound; cout << "Enter a positive integer: "; cin >> bound; double sum = 0.0; int i = 0; while ( sum < bound )

sum += 1.0/++i;
cout << "The sum of the first " <<i << " reciprocals is "
 << sum << endl;
}

3

ทดสอบโปรแกรม

int main() { int bound; cout << "Enter a positive integer: "; cin >> bound; double sum = 0.0; int i = 0; while ( sum < bound )

sum += 1.0/++i;
cout << "The sum of the first " <<i << " reciprocals is "
 << sum << endl;
}

4

โปรแกรมนี้จะพิมพ์ตารางแม่สูตรคูณ โดยการวนลูปแต่ละรอบของลูปนอก x จะพิมพ์แถวหนึ่งแถวของตารางแม่สูตรคูณ ยกตัวอย่างเช่น ในรอบแรกเมื่อ x=1 ลูปใน y จะวนซ้ำ 12 ครั้งโดยจะพิมพ์ค่า 1*y ของค่าจาก 1 ถึง 12 หลังจากนั้นในรอบที่สองของลูปนอก x เมื่อ x=2 ลูปใน y ก็วนซ้ำอีก 12 รอบเช่นเดิม โดยในครั้งนี้จะพิมพ์ค่า 2*y สำหรับทุกค่า y จาก 1 ถึง 12 คำสั่ง cout « endl ที่แยกออกมาอยู่ภายในลูปนอก และอยู่ภายนอกลูปใน เพื่อที่จะทำให้เกิดบรรทัดหนึ่งใหม่ เมื่อสิ้นสุดการวนรอบแต่ละครั้งของลูปนอก

โปรแกรมนี้ใช้ stream manipulator setw ในการกำหนดความกว้างของช่วงข้อมูลที่จะพิมพ์ออกไปสำหรับจำนวนเต็มแต่ละตัว โดยที่นิพจน์ setw(4) หมายถึง กำหนดการแสดงผลข้อมูลอย่างน้อย 4 ตำแหน่ง สำหรับข้อมูลที่จะแสดงตัวถัดไป การจัดการแบบนี้ทำให้ข้อมูลที่จะแสดงออกไปถูกจัดอยู่ในรูปตารางที่อ่านง่ายขนาด 12 สดมภ์ โดยที่ ข้อมูลจำนวนเต็มแต่ละตัวจะถูกแสดงชิดขวา การจัดการข้อมูลการแสดงผล setw ( ) ถูกนิยามอยู่ในไฟล์ส่วนหัว <iomanip> เพราะฉะนั้น โปรแกรมนี้ต้องมีคำสั่งประกาศ

int main() { int bound; cout << "Enter a positive integer: "; cin >> bound; double sum = 0.0; int i = 0; while ( sum < bound )

sum += 1.0/++i;
cout << "The sum of the first " <<i << " reciprocals is "
 << sum << endl;
}

5

ด้วย นอกเหนือจากไฟล์ส่วนหัว <iostream>

ตัวอย่างที่ 4.19 การตรวจสอบ loop invariant

int main() { int bound; cout << "Enter a positive integer: "; cin >> bound; double sum = 0.0; int i = 0; while ( sum < bound )

sum += 1.0/++i;
cout << "The sum of the first " <<i << " reciprocals is "
 << sum << endl;
}

6

ทดสอบโปรแกรม โดยป้อนค่า n = 63

int main() { int bound; cout << "Enter a positive integer: "; cin >> bound; double sum = 0.0; int i = 0; while ( sum < bound )

sum += 1.0/++i;
cout << "The sum of the first " <<i << " reciprocals is "
 << sum << endl;
}

7

โปรแกรมนี้จะคำนวณและพิมพ์ จำนวนเต็มบวกที่มากที่สุดที่น้อยกว่าหรือเท่ากับค่าลอการิทึมฐานสองของจำนวนเต็มที่ผู้ใช้ป้อนเข้าไป (discrete binary logarithm) โดยที่จะมีการตรวจสอบ loop invariant แล้วพิมพ์ค่าที่เกี่ยวข้องในแต่ละรอบ

ค่า discrete binary logarithm คำนวณจาก จำนวนครั้งที่จำนวนเต็มที่ป้อนเข้ามาสามารถหารด้วย 2 ไปได้เรื่อยๆ ก่อนจะมีค่าเป็น 1 ดังนั้นคำสั่ง for จึงกำหนดค่าเริ่มต้นของตัวแปร i ให้เป็น n หลังจากนั้น จึงหาร i ด้วย 2 ในแต่ละรอบ เมื่อสิ้นสุดการวนรอบ ค่าของ c จะเป็นค่าของ discrete binary logarithm ของ n

นอกเหนือจาก การใช้ฟังก์ชัน setw ( ) ที่ถูกนิยามในไฟล์ส่วนหัว <iomanip> แล้ว โปรแกรมยังได้ใช้ฟังก์ชัน log ( ) ซึ่งถูกนิยามในไฟล์ส่วนหัว <cmath> ซึ่งฟังก์ชันนี้จะส่งค่าลอการิทึมฐาน e ของ n (log(n) = loge n = ln n) ซึ่งจะถูกใช้ในนิพจน์ log(n) / log(2) เพื่อที่จะคำนวณหาค่าลอการิทึมฐาน 2 ของ n : log2n = lg n = (ln n) / (ln 2) ผลลัพธ์ที่พิมพ์ออกมาจะเปรียบเทียบค่า discrete binary logarithm กับค่า continuous binary logarithm โดยที่ค่าแรกจะมีค่าเท่ากับค่าหลังที่ถูกปัดเศษลงให้ใกล้กับจำนวนเต็มมากที่สุด

loop invariant ในตัวอย่างนี้ คือ เงื่อนไข 2^d ⇐ n / i < 2 * 2^d ซึ่งถูกตรวจสอบจากการพิมพ์ค่านิพจน์ทั้งสาม p2d, n และ 2 * p2d โดยที่ p2d ได้มาจากการคำนวณด้วยฟังก์ชันยกกำลัง pow ( ) ซึ่งถูกนิยามในไฟล์ส่วนหัว <cmath>

เราสามารถพิสูจน์ได้ว่าคำสั่ง for นี้ จะคำนวณหาค่า discrete binary logarithm ได้ถูกต้องเสมอ ในตอนเริ่มต้น d = 0 และ i = n ดังนั้น 2d = 20 = 1, n / i = n / n = 1 และ 2×2d = 2×20 = 2 ดังนั้น 2d ⇐ n / i < 2×2d ในแต่ละรอบค่า d จะเพิ่มขึ้น และ i จะถูกทอนลงครึ่งหนึ่ง ทำให้ค่า n / i จะเพิ่มขึ้นเป็นสองเท่า ดังนั้นเงื่อนไข 2d ⇐ n / i < 2×2d จะไม่เปลี่ยนแปลง เช่น มันเป็นจริงในตอนแรก และมันคงเป็นจริงตลอดช่วงของการวนรอบ เมื่อลูปสิ้นสุดการทำงาน ค่า i = 1 ทำให้เงื่อนไขเปลี่ยนเป็น 2d ⇐ n / 1 < 2×2d ซึ่งมีค่าเท่ากับ 2d ⇐ n < 2×2d ลอการิทึมของนิพนจ์นี้คือ d = lg(2d) ⇐ lg n < lg(2d+1) = d + 1 ทำให้ d เป็นจำนวนเต็มที่มากที่สุดที่ ⇐ lg n

4.5 คำสั่ง break

ในบทที่ 3 แสดงการใช้คำสั่ง break ในคำสั่ง switch เพื่อให้จบการทำงานของคำสั่ง switch ในทันที แล้วไปประมวลผลคำสั่งถัดไป ในทำนองเดียวกัน ผู้เขียนโปรแกรมสามารถใช้คำสั่ง switch ในลูปได้ โดยที่ เมื่อคำสั่งนี้ถูกประมวลผล มันจะหยุดการทำงานของลูป และ ออกจากการวนรอบออกไปประมวลผลคำสั่งถัดไปทันที

ตัวอย่างที่ 4.20 การใช้คำสั่ง break เพื่อหยุดลูป

int main() { int bound; cout << "Enter a positive integer: "; cin >> bound; double sum = 0.0; int i = 0; while ( sum < bound )

sum += 1.0/++i;
cout << "The sum of the first " <<i << " reciprocals is "
 << sum << endl;
}

8

ทดสอบโปรแกรม โดยป้อนค่า n = 8

Enter a positive integer: 8 The sum of the first 8 integers is 36

โปรแกรมนี้จะมีผลการทำงานเช่นเดียวกับตัวอย่างที่ 4.1 แต่ใช้คำสั่ง break ในการควบคุมลูป โดยลูปจะวนทำงานไปเรื่อย ๆ ตามเงื่อนไข (true) แต่ถ้าเมื่อใดที่นิพจน์ (i>n) มีค่าเป็นไม่เป็นศูนย์ (จริง) คำสั่ง break จะถูกประมวลผล ก่อให้เกิดการหยุดการวนรอบ

คำสั่ง break ทำให้เกิดความยืดหยุ่นในการควบคุมลูป ซึ่งตามปกติคำสั่ง while หรือ do…while หรือ for จะหยุดการทำงานเฉพาะในส่วนเริ่มต้นหรือส่วนสุดท้ายของลูป แต่คำสั่ง break สามารถปรากฏที่ใดก็ได้ในส่วนคำสั่งภายในลูป กล่าวคือ ผู้เขียนโปรแกรมสามารถเขียนคำสั่ง ให้หยุดการทำงานของลูปที่ใดก็ได้ภายในลูป ซึ่งจะแสดงให้เห็นดังตัวอย่างต่อไปนี้

ตัวอย่างที่ 4.21 การควบคุมการนำเข้าข้อมูลแบบ sentinel

Enter a positive integer: 3 The sum of the first 11 reciprocals is 3.01988

0

ทดสอบโปรแกรม โดยป้อนค่า n = 4, 7, 1, 5, 2 และ 0 ตามลำดับ

Enter a positive integer: 3 The sum of the first 11 reciprocals is 3.01988

1

โปรแกรมนี้จะนำเข้าชุดข้อมูลจำนวนเต็มบวก โดยตัวสุดท้าย คือ 0 ซึ่งหมายความว่าหมดข้อมูล แล้วพิมพ์ค่าเฉลี่ยของชุดข้อมูล ไม่รวมค่า 0) ทางจอภาพ เมื่อค่าตัวแปร n มีค่าเป็น 0 คำสั่ง break จะถูกประมวลผล ทำให้ลูปหยุดการทำงาน แล้วย้ายการทำงานไปยังคำสั่งการแสดงผล ถ้าไม่มีคำสั่ง break ผู้เขียนโปรแกรมต้องเขียนคำสั่ง ++count ไว้ในส่วนที่เป็นเงื่อนไขควบคุมการวนรอบ ไม่เช่นนั้น ผู้เขียนโปรแกรมต้องเขียนคำสั่งให้ปรับปรุงค่าของตัวแปร count โดยให้ค่าในตัวแปร count ลดลง 1 ก่อนนำไปแสดงผล หรือ กำหนดให้ตัวแปร count มีค่าเริ่มต้นเป็น -1

ให้สังเกตว่า ทั้งสามส่วนของกลไกควบคุมในคำสั่ง for นั้นว่าง ( ; ; ) โครงสร้างนี้ หมายถึง การควบคุมให้เกิดการวนรอบไม่รู้จบในคำสั่ง for และ ถ้าไม่มีคำสั่ง break ลูปนี้จะวนรอบแบบไม่รู้จบ

คำสั่ง break เมื่อถูกใช้ในลูปที่ซ้อนกัน ผลของมันจะกระทบในลูปที่มันถูกกำหนดอยู่เท่านั้น เช่น ถ้าคำสั่ง break ปรากฏอยู่ในลูปใน ส่วนลูปนอกก็ยังคงทำงานต่อไป โดยไม่ได้รับผลจากคำสั่ง break นั้นเลย ซึ่งแสดงให้เห็นดังตัวอย่างต่อไปนี้

ตัวอย่างที่ 4.22 การใช้คำสั่ง break กับลูปที่ซ้อนกัน

Enter a positive integer: 3 The sum of the first 11 reciprocals is 3.01988

2

ทดสอบโปรแกรม

Enter a positive integer: 3 The sum of the first 11 reciprocals is 3.01988

3

เนื่องจาก ตัวดำเนินการคูณมีความสัมพันธ์กันตามกฎของการสลับที่ (เช่น 3´4 = 4´3) ตารางแม่สูตรคูณส่วนใหญ่จึงมักจะละส่วนด้านบนที่เหนือแนวทะแยงมุมหลัก โปรแกรมนี้ได้ถูกพัฒนาต่อจากตัว อย่างที่ 4.18 เพื่อทำการพิมพ์ตารางแม่สูตรคูณแบบสามเหลี่ยม

เมื่อ y > x การทำงานในลูปใน y จะถูกระงับ และไปเริ่มต้นในรอบใหม่ของลูปนอก x ยกตัวอย่างเช่น เมื่อ x = 3 ลูป y จะวนทำ 3 ครั้ง (ด้วยค่า y = 1, 2 และ 3 ตามลำดับ) เพื่อพิมพ์ค่า 3 6 9 โดยในรอบที่ 4 ของลูป y เงื่อนไข (y>x) เป็นจริง คำสั่ง break จึงทำให้ลูปหยุด และย้ายไปทำคำสั่ง cout « endl (ซึ่งอยู่นอกลูป y) หลังจากนั้นลูปรอบนอก x จะไปเริ่มทำงานในรอบที่ 4 ด้วยค่า x = 4

4.6 คำสั่ง continue

คำสั่ง break จะข้ามคำสั่งที่เหลือในลูป แล้วกระโดดไปทำคำสั่งถัดไปนอกลูปทันที คำสั่ง continue ก็เช่นกัน มันจะข้ามคำสั่งที่เหลือในลูปนั้น แต่แทนที่จะระงับการทำงานของลูปทั้งหมดลง มันจะย้ายการทำงานไปยังรอบถัดไปของลูปนั้นๆ กล่าวคือ มันจะไปทำงานต่อในรอบถัดไป หลังจากข้ามคำสั่งต่างๆในลูปที่เหลือในรอบนั้น

ตัวอย่างที่ 4.23 การใช้คำสั่ง continue และ คำสั่ง break

Enter a positive integer: 3 The sum of the first 11 reciprocals is 3.01988

4

ทดสอบโปรแกรม

Enter a positive integer: 3 The sum of the first 11 reciprocals is 3.01988

5

โปรแกรมนี้เปรียบเทียบผลการทำงานของคำสั่ง continue และ คำสั่ง break เมื่อตัวแปร n มีค่าเป็น 7 เงื่อนไขของ if ทั้งสองตัวเป็นเท็จ ทำให้การทำงานย้ายไปยังส่วนท้ายของลูป เมื่อ n มีค่าเป็น 4 เงื่อนไขของ if ตัวแรกเป็นจริง (4 หาร 2 ลงตัว) ทำให้การทำงานข้ามคำสั่งที่เหลือแล้วกระโดดไปยังส่วนต้นของลูปอีกครั้งทันที เพื่อที่จะเริ่มการทำงานในรอบใหม่ เมื่อ n มีค่าเป็น 9 เงื่อนไขของ if ตัวแรกเป็นเท็จ (9 หาร 2 ไม่ลงตัว) แต่เงื่อนไขตัวที่สองของ if เป็นจริง (9 หาร3 ลงตัว) ทำให้การวนรอบสิ้นสุด และหลุดออกมาจากลูป เพื่อไปประมวลผลคำสั่งถัดไปที่อยู่นอกลูป

4.7 คำสั่ง goto

คำสั่ง break และ คำสั่ง continue เป็นคำสั่งให้โปรแกรมย้ายการประมวลผลไปทำงานในส่วนอื่น นอกเหนือจากลำดับการทำงานตามปกติในคำสั่งนั้น ส่วนจุดหมายปลายทางของคำสั่งที่จะย้ายการทำงานไป จะแตกต่างกันในแต่ละคำสั่ง คือ คำสั่ง break จะไปย้ายการทำงานไปที่คำสั่งแรกนอกลูป ในขณะที่ คำสั่ง continue จะย้ายการทำงานไปส่วนเริ่มต้นของลูปในรอบถัดไป คำสั่งเหล่านี้จัดเป็นคำสั่งในกลุ่ม คำสั่งข้ามกระโดด (jump statements) เนื่องจาก มีผลทำให้เกิดการเปลี่ยนแปลงการทำงานไปยังคำสั่งที่อยู่ในตำแหน่งอื่นๆ ที่ไม่ใช่คำสั่งถัดไป

คำสั่ง goto ก็เป็นอีกหนึ่งคำสั่งข้ามกระโดด ที่ปลายทางของคำสั่งจะถูกกำหนดด้วยฉลาก (label) ซึ่งระบุตำแหน่งโดยผู้เขียนโปรแกรม ฉลากเป็นชื่อที่ตามด้วยเครื่องหมาย : (colon) โดยที่ ผู้เขียนโปรแกรมสามารถวางฉลากไว้หน้าคำสั่งที่ต้องการให้มีการย้ายตำแหน่งไปตามการทำงานของคำสั่ง goto การทำงานของฉลากในภาษา C++ จะคล้ายกับการทำงานของคำสั่ง case ในคำสั่ง switch คือ จะบ่งบอกตำแหน่งปลายทางของการกระโดด

ตัวอย่างที่ 4.22 แสดงให้เห็นถึงการทำงานของคำสั่ง break ในลูปที่ซ้อนกัน การทำงานจะออกมาจากลูปในสุดที่คำสั่ง break อยู่ และ กระโดดไปยังลูปในถัดไปที่ซ้อนกัน ถ้าต้องการย้ายการทำงานจากลูปในสุดไปยังลูปนอก ต้องอาศัยคำสั่ง goto ดังจะเห็นจากตัวอย่างต่อไปนี้

ตัวอย่างที่ 4.24 การใช้คำสั่ง goto เพื่อออกจากลูปที่ซ้อนกัน

Enter a positive integer: 3 The sum of the first 11 reciprocals is 3.01988

6

ทดสอบโปรแกรม

Enter a positive integer: 3 The sum of the first 11 reciprocals is 3.01988

7

เมื่อการทำงานมาถึงคำสั่ง goto ซึ่งอยู่ภายในลูป k (ลูปในสุด) โปรแกรมจะกระโดดไปยังคำสั่งที่มีฉลากที่อยู่ส่วนท้ายของลูป i (ลูปนอก) เนื่องจากคำสั่งนั้นเป็นคำสั่งสุดท้ายในลูป i การทำงานจึงวนกลับไปยังส่วนต้นลูป i อีกรอบหนึ่ง เมื่อ i และ j มีค่าเป็น 0 ลูป k จะวนซ้ำ 5 ครั้ง เพื่อแสดงค่า 0 1 2 3 4 ตามด้วยเครื่องหมาย * หลังจากนั้น j จะเพิ่มขึ้นเป็น 1 และ ลูป k ก็วนอีก 5 รอบเพื่อพิมพ์ 1 2 3 4 5 แล้วตามด้วย * หลังจากนั้น j จะเพิ่มขึ้นเป็น 2 และลูป k ก็วนอีก 4 รอบเพื่อพิมพ์ 2 3 4 5 แต่รอบถัดไปของลูป k i มีค่าเป็น 0 j มีค่าเป็น 2 และ k มีค่าเป็น 4 ทำให้ i+j+k = 6 มีผลให้คำสั่ง goto ทำงานเป็นครั้งแรก ทำให้การทำงานกระโดดไปยังคำสั่งที่มีฉลาก esc: เพื่อพิมพ์จุด แล้วขึ้นบรรทัดใหม่ สังเกตว่าทั้งลูป j และลูป k จะถูกระงับก่อนที่จะจบการทำงานครบทุกรอบ

ต่อมาในรอบ i = 1 ลูป j ก็มาเริ่มทำงานอีกครั้งด้วย j = 0 ลูป k จะทำงาน 5 รอบเพื่อพิมพ์ 1 2 3 4 5 ตามด้วย * หลังจากนั้น j จะเพิ่มขึ้นเป็น 1 และลูป k ก็วนอีก 4 รอบเพื่อพิมพ์ 2 3 4 5 หลังจากนั้นในรอบถัดไปของลูป k i มีค่าเป็น 1 j มีค่าเป็น 2 และ k มีค่าเป็น 3 ทำให้ i+j+k = 6 มีผลให้คำสั่ง goto ถูกประมวลผลเป็นครั้งที่สอง และ อีกครั้งที่การทำงานกระโดดไปทำที่คำสั่งที่มีฉลากทันที เพื่อพิมพ์จุด แล้วขึ้นบรรทัดใหม่

ใน 3 รอบถัดมาของลูป i ลูปใน k จะไม่มีทางที่จะวนจนครบรอบการทำงานของมัน เนื่องจาก i+j+k ยังไงก็มีค่าเกิน 5 (เนื่องจาก i มีค่ามากกว่า 2 ) ทำให้ไม่มีการพิมพ์ * อีกต่อไป

ให้สังเกตว่า คำสั่งที่มีฉลากสามารถปรากฏอยู่ภายในลูป หรือ ภายนอกของลูปก็ได้ ซึ่งการกระทำในกรณีหลังนี้จะทำให้คำสั่ง goto ระงับการทำงานของลูปทั้งหมดที่ซ้อนกัน โดยทั่วไป การเขียนคำสั่งที่มีฉลากจะเขียนเยื้องออกไปซ้ายกว่าปกติ เพื่อให้มองเห็นได้ง่ายขึ้น ซึ่งถ้าคำสั่งนั้นไม่ได้มีฉลาก เช่น

Enter a positive integer: 3 The sum of the first 11 reciprocals is 3.01988

8

แทนที่

Enter a positive integer: 3 The sum of the first 11 reciprocals is 3.01988

9

ตัวอย่างที่ 4.24 แสดงให้เห็นถึงวิธีในการออกจากลูปที่ซ้อนกัน อีกวิธีหนึ่งที่สามารถออกจากลูปได้ คือ การใช้ flag ซึ่ง flag ก็คือ ตัวแปรชนิดตรรกะที่มีการตั้งค่าเริ่มต้นเป็นเท็จ และ ภายหลังเมื่อตรวจพบว่าเงื่อนไขบางอย่างเป็นจริง ค่า flag ก็จะถูกเปลี่ยนให้มีค่าเป็นจริง เพื่อบ่งชี้ถึงเป็นภาวะพิเศษ โดย ผู้เขียนโปรแกรมสามารถเขียนการทำงานของโปรแกรม ให้ถูกขัดจังหวะเมื่อค่าของ flag มีค่าเป็นจริง ซึ่งจะแสดงให้เห็นในตัวอย่างต่อไปนี้

ตัวอย่างที่ 4.25 การใช้ flag เพื่อออกจากลูปที่ซ้อนกัน

int main() { double x; cout << "Enter a positive number: "; cin >> x; while ( x > 0 ) { cout << "sqrt(" << x << ") = " << sqrt(x) << endl;

cout << "Enter another positive number (or 0 to quit): ";
cin >> x;
}
}

0

ทดสอบโปรแกรม

int main() { double x; cout << "Enter a positive number: "; cin >> x; while ( x > 0 ) { cout << "sqrt(" << x << ") = " << sqrt(x) << endl;

cout << "Enter another positive number (or 0 to quit): ";
cin >> x;
}
}

1

โปรแกรมนี้มีผลรันคล้ายกับในตัวอย่างที่ 4.24 เมื่อ flag ที่ชื่อว่า done มีค่าเป็นจริง ทั้งลูปในสุด k และ ลูปกลาง j จะถูกระงับ ออกไปยังลูปนอก i เพื่อจบการทำงานในรอบนั้น ด้วยการพิมพ์จุด แล้วขึ้นบรรทัดใหม่ ผู้เขียนโปรแกรมต้องไม่ลืมที่จะทำการตั้งค่าให้กับ done ใหม่ ให้กลับไปเป็นเท็จก่อนที่จะเริ่มการทำงานในรอบถัดไป

4.8 การสร้างจำนวนตัวเลขสุ่มเทียม (pseudo-random numbers)

การประยุกต์ใช้งานทางคอมพิวเตอร์ที่สำคัญอย่างหนึ่ง คือ การจำลองระบบการทำงานจริง การวิจัยและพัฒนาที่นำสมัยส่วนใหญ่จำเป็นต้องอาศัยเทคโนโลยีของการจำลอง เพื่อศึกษาว่าระบบทำงานได้อย่างไร โดยปราศจากการทำงานโต้ตอบกับระบบจริงๆ

การจำลองตัวเลขสุ่มเป็นการจำลองการทำงานที่ไม่แน่นอนของระบบในโลกแห่งความจริง และแน่ นอนว่าคอมพิวเตอร์ไม่สามารถที่จะสร้างตัวเลขสุ่มที่แท้จริงออกมาได้ เนื่องจาก การทำงานของคอมพิว เตอร์มีลักษณะแบบกำหนดแน่นอน (deterministic) กล่าวคือ สำหรับคอมพิวเตอร์หนึ่งๆ ถ้าผู้ใช้ป้อนข้อมูลชุดเดิมเข้าไปประมวลผล ก็จะได้ผลลัพธ์ชุดเดิมออกมาเสมอ ดังนั้น การสร้างตัวเลขสุ่มในคอมพิวเตอร์จึงเป็นการสร้างตัวเลขสุ่มแบบเสมือน ให้เกิดชุดตัวเลขที่มีการกระจายที่หลากหลาย ซึ่งตัวเลขเหล่านี้ เรียกว่า ตัวเลขสุ่มเทียม (pseudo-random numbers)

ไฟล์ส่วนหัว <cstdlib> ในภาษา C มาตรฐาน นิยามฟังก์ชัน rand ( ) ในการสร้างจำนวนเต็มสุ่มเทียมในช่วงจาก 0 ถึงค่า RAND_MAX ซึ่งเป็นค่าคงที่ที่ถูกนิยามไว้ในไฟล์ส่วนหัว <cstdlib> โดยที่ ในแต่ละครั้งที่เรียกฟังก์ชัน rand ( ) โปรแกรมจะสร้างจำนวนเต็มแบบ unsigned ในช่วงดังกล่าวให้ 1 ค่า

ตัวอย่างที่ 4.26 การสร้างจำนวนตัวเลขสุ่มเทียม

int main() { double x; cout << "Enter a positive number: "; cin >> x; while ( x > 0 ) { cout << "sqrt(" << x << ") = " << sqrt(x) << endl;

cout << "Enter another positive number (or 0 to quit): ";
cin >> x;
}
}

2

ทดสอบโปรแกรม ครั้งที่ 1 ทดสอบโปรแกรม ครั้งที่ 2

int main() { double x; cout << "Enter a positive number: "; cin >> x; while ( x > 0 ) { cout << "sqrt(" << x << ") = " << sqrt(x) << endl;

cout << "Enter another positive number (or 0 to quit): ";
cin >> x;
}
}

3

int main() { double x; cout << "Enter a positive number: "; cin >> x; while ( x > 0 ) { cout << "sqrt(" << x << ") = " << sqrt(x) << endl;

cout << "Enter another positive number (or 0 to quit): ";
cin >> x;
}
}

3

โปรแกรมนี้ใช้ฟังก์ชัน rand ( ) ในการสร้างจำนวนตัวเลขสุ่มเทียม ในแต่ละครั้งของการประมวลผล คอมพิวเตอร์จะสร้างจำนวนเต็มแบบ unsigned 8 ตัว ซึ่งได้จากการกระจายที่มีรูปแบบเดียวกันในช่วง 0 ถึง RAND_MAX ซึ่งมีค่าเท่ากับ 2,147,483,647 บนคอมพิวเตอร์เครื่องนี้ อย่างไรก็ดี ในแต่ละครั้งของการประมวลผล จะได้ผลรันชุดเดิมเสมอ ทั้งนี้เนื่องจาก มันถูกสร้างมาจากเมล็ด (seed) เดียวกัน

ตัวเลขสุ่มเทียมแต่ละตัวจะถูกสร้างจากตัวเลขสุ่มเทียมตัวก่อนหน้า โดยทำการปรับเปลี่ยนค่าเดิมด้วยวิธีพิเศษแบบ “number crunching” ที่ถูกนิยามภายใน ค่าตัวเลขสุ่มเทียมตัวแรกจะถูกสร้างจากตัวแปรที่ถูกนิยามไว้ในชื่อ seed ซึ่งโดยปกติค่า seed จะถูกนิยามเป็นค่าเฉพาะเดิมๆ ทุกครั้งเมื่อเริ่มการประมวลผล ดังนั้น ผู้เขียนโปรแกรมสามารถใช้ฟังก์ชัน srand ( ) ในการตั้งค่า seed ขึ้นมาเองได้ เพื่อให้ได้มาซึ่งตัวเลขสุ่มเทียมที่ใกล้ค่าสุ่มจริงมากที่สุด

ตัวอย่างที่ 4.27 การตั้งค่า seed แบบโต้ตอบได้ (interactively)

int main() { double x; cout << "Enter a positive number: "; cin >> x; while ( x > 0 ) { cout << "sqrt(" << x << ") = " << sqrt(x) << endl;

cout << "Enter another positive number (or 0 to quit): ";
cin >> x;
}
}

5

ทดสอบโปรแกรม เมื่อป้อนค่า seed = 0 ทดสอบโปรแกรม เมื่อป้อนค่า seed = 1 ทดสอบโปรแกรม เมื่อป้อนค่า seed = 12345

int main() { double x; cout << "Enter a positive number: "; cin >> x; while ( x > 0 ) { cout << "sqrt(" << x << ") = " << sqrt(x) << endl;

cout << "Enter another positive number (or 0 to quit): ";
cin >> x;
}
}

6

int main() { double x; cout << "Enter a positive number: "; cin >> x; while ( x > 0 ) { cout << "sqrt(" << x << ") = " << sqrt(x) << endl;

cout << "Enter another positive number (or 0 to quit): ";
cin >> x;
}
}

7

int main() { double x; cout << "Enter a positive number: "; cin >> x; while ( x > 0 ) { cout << "sqrt(" << x << ") = " << sqrt(x) << endl;

cout << "Enter another positive number (or 0 to quit): ";
cin >> x;
}
}

8

โปรแกรมนี้เหมือนกับในตัวอย่างที่ 4.26 เพียงแต่ว่า ในโปรแกรมนี้ตัวสร้างจำนวนตัวเลขสุ่มเทียมสามารถตั้งค่า seed แบบโต้ตอบได้ โดยให้ผู้ใช้เป็นผู้ป้อนค่า ในบรรทัด srand (seed) เป็นการให้ค่าตัวแปร seed แก่ค่า “seed” ภายในใหม่ เพื่อนำไปใช้เป็นค่าเริ่มต้นในฟังก์ชัน rand ( ) เพื่อใช้ในการสร้างตัวเลขสุ่มเทียม ค่า seed ที่แตกต่างกันจะทำให้ได้มาซึ่งผลลัพธ์ที่แตกต่างกัน

ให้สังเกตว่า เมื่อให้ค่า seed มีค่าเป็น 12345 (ค่าตัวเลขสุ่มตัวแรกที่ได้จากการประมวลผลในครั้งแรก) ในการประมวลผลครั้งที่สาม ตัวเลขแรกที่ถูกสร้างขึ้นโดยฟังก์ชัน rand ( ) คือ ค่า 1406932606 ซึ่งเป็นค่าที่สองของชุดตัวเลขสุ่มในการประมวลผลครั้งแรกนั่นเอง ทำให้ชุดตัวเลขสุ่มลำดับที่ 1 ถึง 7 ของการประมวลผลครั้งที่สาม มีค่าเหมือนลำดับที่ 2 ถึง 8 ของการประมวลผลครั้งแรก นอกจากนี้ ผลที่ได้จากการประมวลผลในครั้งที่สอง มีค่าเหมือนกับตัวอย่างที่ 4.26 นั่นหมายความว่าค่า seed ตามปกติของเครื่องคอมพิวเตอร์เครื่องนี้มีค่าเท่ากับ 1

อย่างไรก็ดี แม้ว่าโปรแกรมตัวอย่าง 4.27 จะสามารถสร้างชุดตัวเลขสุ่มที่แตกต่างกันได้ตามค่าที่ผู้ใช้ป้อน ผู้ใช้โปรแกรมก็ยังสามารถคาดเดาคำตอบได้จากค่าที่ตัวเองป้อนเข้าสู่เครื่อง ซึ่งขัดกับธรรมชาติของตัวเลขสุ่ม ที่ไม่ควรคาดเดาได้ว่าจะเป็นตัวเลขใด ปัญหาดังกล่าวสามารถแก้ไขได้โดยใช้นาฬิการะบบในคอมพิวเตอร์ ซึ่งนาฬิการะบบนี้จะเก็บเวลาปัจจุบันในหน่วยวินาที ฟังก์ชัน time ( ) ที่นิยามในไฟล์ส่วนหัว <ctime> จะส่งค่าตัวเลขปัจจุบันในรูปของจำนวนเต็มแบบ unsigned ซึ่งตัวเลขนี้สามารถนำมาใช้เป็นค่า เริ่มต้นให้กับตัวแปร seed เพื่อใช้เป็นค่าเริ่มต้นของฟังก์ชัน rand() อีกที

ตัวอย่างที่ 4.28 การตั้งค่า seed จากนาฬิการะบบ

int main() { double x; cout << "Enter a positive number: "; cin >> x; while ( x > 0 ) { cout << "sqrt(" << x << ") = " << sqrt(x) << endl;

cout << "Enter another positive number (or 0 to quit): ";
cin >> x;
}
}

9

ทดสอบโปรแกรม ครั้งที่ 1 ทดสอบโปรแกรม ครั้งที่ 2

Enter a positive number: 49 sqrt(49) = 7 Enter another positive number (or 0 to quit): 3.14159 sqrt(3.14159) = 1.77245 Enter another positive number (or 0 to quit): 100000 sqrt(100000) = 316.228 Enter another positive number (or 0 to quit): 0

0

Enter a positive number: 49 sqrt(49) = 7 Enter another positive number (or 0 to quit): 3.14159 sqrt(3.14159) = 1.77245 Enter another positive number (or 0 to quit): 100000 sqrt(100000) = 316.228 Enter another positive number (or 0 to quit): 0

1

โปรแกรมนี้มีผลการทำงานเหมือนกับตัวอย่างที่ 4.27 เพียงแต่ในโปรแกรมนี้ตัวสร้างจำนวนตัวเลขสุ่มเทียมจะได้ค่า seed จากนาฬิการะบบ ในครั้งแรกของการประมวลผล ฟังก์ชัน time ( ) ได้ค่าจำนวนเต็ม 808,148,157 ซึ่งเป็นค่าของนาฬิการะบบในขณะที่ประมวลผล ค่าดังกล่าวจะถูกใช้เป็นค่าเริ่มต้นของตัวแปร seed ในการสร้างตัวเลขสุ่มเทียม การประมวลผลในครั้งที่สอง กระทำในอีก 3 วินาทีต่อมา ทำให้ฟังก์ชั่น time ( ) มีค่าเป็น 808,148,160 ผลที่ตาม โปรแกรมจะสร้างค่าตัวเลขสุ่มเทียมอีกชุด ที่มีค่าแตกต่างกัน โดยที่ผู้ใช้ไม่สามารถบอกได้ล่วงหน้าว่าเป็นเท่าไร ทำให้โปรแกรมในตัวอย่าง 4.28 สามารถใช้สร้างตัวเลขสุ่มเทียม ที่ไม่สามารถบอกได้ว่าเป็นค่าใด ขึ้นกับเวลาที่ประมวลผล ดังนั้น การประมวลผลโปรแกรมแต่ละครั้ง จะได้ชุดตัวเลขสุ่มชุดใหม่เสมอ เช่น

ทดสอบโปรแกรม ครั้งที่ 3 ทดสอบโปรแกรม ครั้งที่ 4

Enter a positive number: 49 sqrt(49) = 7 Enter another positive number (or 0 to quit): 3.14159 sqrt(3.14159) = 1.77245 Enter another positive number (or 0 to quit): 100000 sqrt(100000) = 316.228 Enter another positive number (or 0 to quit): 0

2

Enter a positive number: 49 sqrt(49) = 7 Enter another positive number (or 0 to quit): 3.14159 sqrt(3.14159) = 1.77245 Enter another positive number (or 0 to quit): 100000 sqrt(100000) = 316.228 Enter another positive number (or 0 to quit): 0

3

โปรแกรมจำลองส่วนใหญ่มักต้องการสร้างตัวเลขสุ่มที่มีค่าแน่นอนในพิสัยข้อมูลที่กำหนด ตัวอย่างต่อไปนี้จะแสดงการปรับค่าตัวเลขสุ่มที่ได้ให้เป็นค่าที่อยู่ในช่วงที่ผู้ใช้ต้องการ

ตัวอย่างที่ 4.29 การสร้างจำนวนตัวเลขสุ่มเทียมในช่วงที่กำหนด

Enter a positive number: 49 sqrt(49) = 7 Enter another positive number (or 0 to quit): 3.14159 sqrt(3.14159) = 1.77245 Enter another positive number (or 0 to quit): 100000 sqrt(100000) = 316.228 Enter another positive number (or 0 to quit): 0

4

ทดสอบโปรแกรม

Enter a positive number: 49 sqrt(49) = 7 Enter another positive number (or 0 to quit): 3.14159 sqrt(3.14159) = 1.77245 Enter another positive number (or 0 to quit): 100000 sqrt(100000) = 316.228 Enter another positive number (or 0 to quit): 0

5

ทดสอบโปรแกรม

Enter a positive number: 49 sqrt(49) = 7 Enter another positive number (or 0 to quit): 3.14159 sqrt(3.14159) = 1.77245 Enter another positive number (or 0 to quit): 100000 sqrt(100000) = 316.228 Enter another positive number (or 0 to quit): 0

6

โปรแกรมนี้มีผลการทำงานเหมือนกับตัวอย่างที่ 4.28 ยกเว้นตัวเลขสุ่มเทียมที่ถูกสร้างขึ้นมีขอบเขตในช่วงที่กำหนดไว้ การประมวผลในครั้งแรกสร้างจำนวนเต็ม 20 จำนวน อย่างกระจัดกระจายระหว่างค่าจำนวนเต็ม 1 ถึง 100 ส่วนการประมวลผลในครั้งที่สองสร้างจำนวนเต็ม 20 จำนวน อย่างกระจัดกระจายระหว่างค่าจำนวนเต็ม 22 ถึง 66

ในคำสั่ง for เราหารค่า rand ( ) ด้วย 100 ก่อน เพื่อกำจัดเลขนัยสำคัญสองหลักขวาสุด (หลักสิบและหลักหน่วย) ของตัวเลขสุ่ม เพื่อเป็นการลดปัญหาที่ฟังก์ชันการสร้างตัวเลขสุ่ม จะสร้างตัวเลขสลับกันไปมาระหว่างเลขคู่และเลขคี่ หลังจากนั้น ตัวดำเนินการ % range จะปรับค่าตัวเลขสุ่มให้อยู่ในช่วง 0 ถึง range - 1 และ เมื่อนำค่าที่ได้ไป + min จะทำให้เกิดตัวเลขสุ่มที่อยู่ในช่วงระหว่าง min ถึง max