4. לולאות

שתי פקודות התנאי שראינו בפרק שעבר (if-else ו- switch), מהוות יחד עם פקודות הלולאה, שנכיר בפרק הנוכחי את אוסף פקודות הבקרה (control statements) הקיימות בשפה. פקודות בקרה, באופן כללי, מבקרות את מהלך התקדמות התכנית: אילו פקודות מתוך הנכללות בתכנית תבוצענה בהרצה כלשהי, וכמה פעמים הן תבוצענה. מתוכן פקודות התנאי גורמות לכך שפקודות כלשהן תבוצענה או לא תבוצענה, ופקודות הלולאה גורמות לכך שפקודות כלשהן תבוצענה כמה וכמה פעמים באופן מחזורי. שפת C מעמידה לרשותנו שלוש פקודות לולאה: while, for, do-while.

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

4.1 לולאת while

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

4.1.1 דוגמות ראשונות

קטע התכנית הראשונה הכוללת לולאת while

cin >> num1 >> num2 ;

while (num1 <= num2) {

cout << num1 << “ “ ;

num1++ ;

}

cout << "bye\n" ;

הסבר כתוב אודות הדוגמה הראשונה לשימוש בלולאת while

4.1.1.pdf

תרגול עצמי בסיסי בנושא לולאת while

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

כלומר הפלט יראה:

10 8 6 4 2

20 18 16 14 12

...

4.1.2 הדפסת כפולות של מספר

קוד התכנית המדפיסה כפולות של מספר

#include <iostream>


using std::cin ;

using std::cout ;

using std::endl ;


int main()

{

int num,

counter = 1 ;


cin >> num ;


while (counter <= 10) {

cout << num * counter << ' ' ;

counter++ ;

}

cout << endl ;


return 0 ;

}

הסבר כתוב אודות התכנית המדפיסה כפולות של מספר

4.1.2.pdf

4.1.3 הדפסת לוח הכפל

קוד התכנית המדפיסה את לוח הכפל

#include <iostream>


using std::cout ;

using std::endl ;


int main()

{

int num =1,

mul ;


while (num <= 10) {

mul = 1 ;

while (mul <= 10) {

cout << num * mul << ' ' ;

mul++ ;

}

cout << endl ;

num++ ;

}


return 0 ;

}

הסבר כתוב אודות התכנית המדפיסה את לוח הכפל

4.1.3.pdf

תרגול עצמי בסיסי בנושא לולאות מקוננות

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

לדוגמה: אם הקלט היה 3 (שורות) 5 (אורך כל שורה) הפלט יהיה:

*****

*****

*****

4.1.4 + 4.1.5 הדפסת מחלקי מספר וזמן ריצה של תכנית

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

#include <iostream>

#include <cmath>


using std::cin ;

using std::cout ;

using std::endl ;


int main()

{

int num,

div= 2 ;


cin >> num ;

while (div <= sqrt(num)) {

if (num % div == 0)

cout << div << ' ' << num/div << ' ' ;

div++ ;

}

cout << endl ;


return 0 ;

}


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

4.1.4-4.1.5.pdf

4.1.6 בדיקה האם מספר ראשוני (חלק א' - ללא שימוש בפקודת break)

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

cin >> num ;

prime = true ;

divider = 2 ;

while( divider <= sqrt(num))

{

if (num % divider == 0)

prime = false ;

divider++ ;

}

if (prime)

cout << "prime\n" ;

else

cout << "not prime\n" ;

הסבר כתוב אודות תכנית הבודקת האם מספר ראשוני (חלק א' -- ללא שימוש בפקודת break)

4.1.6.pdf

4.1.7 תכנית הבודקת האם מספר ראשוני (חלק ב' --תוך שימוש בפקודת break)

קוד התכניות הבודקות האם מספר ראשוני (חלק ב' --תוך שימוש בפקודת break בשתי דרכים מעט שונות)

// version one

// =========

cin >> num ;

divider = 2 ;

while( divider <= sqrt(num))

{

if (num % divider == 0)

{

prime = false ;

break ;

}

divider++ ;

}

cout << ((prime) ? "prime\n" : "not prime\n") ;


// version two

// =========

cin >> num ;

divider = 2 ;

while (divider <= sqrt(num)){

if (num % divider == 0)

break ;

divider++ ;

}

if (divider <= sqrt(num))

cout << "not prime\n" ;

else cout << "prime" ;

הסבר כתוב אודות התכניות הבודקות האם מספר ראשוני (חלק ב' --תוך שימוש בפקודת break)

4.1.6b.pdf

תרגול עצמי בסיסי בנושא משתנים פקודת ה: break

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

4.1.8 חידוד לגבי פקודת ה: break - הוצאה מהלולאה הפנימית ביותר בלבד

קוד תכנית החידוד לגבי פקודת ה: break - הוצאה מהלולאה הפנימית ביותר בלבד

cin >> num ;

while (num != 0)

{

divider = 2 ;

while( divider <= sqrt(num))

{

if (num % divider == 0)

{

prime = false ;

break ;

}

divider++ ;

}

if (prime)

cout << "prime\n" ;

else

cout << "not prime\n" ;

cin >> num ;

}

הסבר כתוב אודות תכנית החידוד לגבי פקודת ה: break - הוצאה מהלולאה הפנימית ביותר בלבד

4.1.6c.pdf

4.1.9 הדפסת מחלקיו הראשוניים של מספר

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

4.1.7.pdf

4.1.8 ניחוש מספר שהמחשב הגריל, ומושג הקבוע

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

ייצור ערך אקראי

הסבר כתוב אודות ייצור ערך אקראי

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

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

ב. אחרי ש'הפעלנו' את מנגנון יצירת המספרים האקראיים אנו יכולים לקבל ערך אקראי בתחום 0..x (עבור מספר טבעי x), על-ידי הפקודה: rand() %(x +1). הסבר: rand() מחזירה לנו מספר אקראי שהיא בחרה. אם ניקח את שארית החלוקה של המספר שהוגרל ב- x +1, נקבל ערך בתחום שבין אפס ל- x.

ג. כדי שנוכל להשתמש ב- srand() וב- rand() עלינו לכלול בתכניתנו את ההוראה: #include <cstdlib>. כדי שנוכל לבדוק מה מורה שעון המחשב עלינו לכלול את ההוראה: #include <ctime> שתי הוראות אלה תכתבנה בראש התכנית, לצד הוראות ה- include האחרות.

ד. כדי לבדוק מה ערך שעון המחשב נשתמש בפקודה פקודה time(NULL) . במערכות יוניקסיות מחזירה פקודה זאת את מספר השניות שחלפו מאז ה: 1/1/1970, כלומר מספר טבעי שיהיה שונה בכל הרצה והרצה. מספר זה נעביר ל: srand().

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

#include <iostream>

#include <cstdlib>

<include <ctime#

int main()

{

int i = 0 ;

srand((unsigned) time(NULL)); // ‘turn the machine on’

while (i < 10) {

std::cout << rand() % 101 << " " ; //gen a random val

i++;

}

return 0;

}

תרגול עצמי בסיסי בנושא ערכים אקראיים

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

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

קבועים

הסבר כתוב אודות קבועים

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

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

#include <iostream>

#include <cstdlib>

#include <ctime>

const int NUM_OF_STUD = 10, // num of stud in class

MAX_GRADE = 100 ; // highest possible grade

int main()

{

int i = 0 ;

srand((unsigned) time(NULL));

while (i < NUM_OF_STUD) {

std::cout << rand() % (MAX_GRADE +1) << " " ;

i++;

}

return 0;

}

4.1.8.pdf

#include <iostream>

#include <cstdlib>

<include <ctime#

using std::cin ;

using std::cout ;

const int MAX_VAL = 783, // largest possible num to pick

MAX_NUM_OF_GUESSES = 21; // maximal num of gusses user is allowed

int main()

{

int the_num, // the num the computer pick by random

current_guess, // current user guess

guesses_counter = 0 ; // counter of user guesses

srand((unsigned) time(NULL));

the_num = rand() % MAX_VAL ; // pick the number

// guessing loop

// ==========

while (guesses_counter < MAX_NUM_OF_GUESSES) {

cin >> current_guess ;

if (current_guess == the_num) // user guesses it

break ;

guesses_counter++;

}

if (current_guess == the_num)

cout << "You did it, in " << guesses_counter +1 << “ trials\n” ;

else

cout << “Sorry, you failed. the number is: “ << the_num << endl ;

return EXIT_SUCCESS;

}

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

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

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

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

תרגול עצמי בסיסי בנושא קבועים

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

התכנית תציג את לוח הכפל לפי ערכם של שני הקבועים.

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

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

4.1.9 פקודת ה: continue

פקודת ה- continue היא 'בת זוגה הנזנחת' של פקודת ה- break באשר לפחות חלקית היא דומה לאחרונה, אך הרבה פחות שימושית ממנה.

הסבר כתוב אודות פקודת ה: continue

4.1.9.pdf

תרגול עצמי בסיסי בנושא פקודת ה: continue

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

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

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

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

4.2 פקודת ה: for

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

הסבר כתוב אודות לולאת for

4.2.pdf

דוגמה פשוטה ללואת for: סכימת מחלקי מספר

תרגול עצמי בסיסי בנושא לולאת for

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

כלומר הפלט יראה:

10 8 6 4 2

20 18 16 14 12

...

4.2.1 בדיקה האם מספרים טבעיים הם חברים (בשלוש דרכים מעט שונות)

קוד התכנית המציג זוגות של מספרים חברים (גירסה א')

#include <iostream>

#include <cstdlib>


using std::cout ;

using std::endl ;


const int MAX = 1000 ;


int main()

{

int num1, num2,

sum1, sum2 ;


for (num1 = 1; num1 <= MAX; num1++)

for (num2 = 1; num2 <= MAX; num2++)

{

sum1 = 0 ; // calculate dividers of num1

for (int i = 1; i <= num1 /2; i++)

if (num1 % i == 0)

sum1 += i ;


sum2 = 0 ;

for (int i=1; i <= num2 /2; i++)

if (num2 % i == 0)

sum2 += i ;


if (num1 == sum2 && num2 == sum1)

cout << num1 << " " << num2 << endl ;

}

return EXIT_SUCCESS ;

}

בדיקה האם מספרים טבעיים הם חברים (גרסה ב')

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

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

4.2.1.pdf

בדיקה האם מספרים טבעיים הם חברים (גרסה ג' -- יעילה יותר)

הסבר כתוב אודות הבדיקה האם מספרים טבעיים הם חברים (גרסה ג' -- יעילה יותר)

4.2.1a.pdf

תרגול עצמי בסיסי בנושא לולאת for מקוננת

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

לדוגמה: אם נקרא הקלט ארבע, אזי הפלט יהיה:

*

**

***

****

(הפלט יהיה מיושר לשמאל, ולא לימין כמו בדוגמה מעל)


תרגול עצמי של לולת for בדומה לבעיית המספרים החברים

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

  • סכום הריבועים של שני המספרים מהווה חזקה שלישית של מספר טבעי כשלהו
  • סכום החזקות השלישיות של שני המספרים מהווה ריבוע של מספר טבעי כשלהו

אין לעשות שימוש בפונ' מתמטייות כדוגמת: sqrt, pow

כל זוג יוצג פעם יחידה


4.2.2 כמה הערות נוספות על לולאת for (אודות כותרת הלולאה)

הסבר כתוב אודות לולאת for - הערות נוספות

4.2.2.pdf

4.2.3 לולאת for לעומת לולאת while

הסבר כתוב אודות לולאת for לעומת לולאת while

4.2.3.pdf

4.3 לולאת do-while

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

הסבר כתוב אודות לולאת do-while

4.3.pdf

תרגול עצמי בסיסי בנושא לולאת do-while

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

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


4.4 תרגילים

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

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

4.4.pdf