פרק 3 - חזרה ושאלות


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

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

if, else if, ו־else: בחירה אחת או כמה בדיקות

הדיון התכנותי מתחיל בחזרה על תנאים ובמיוחד על ההבדל בין כמה if-ים נפרדים לבין שרשרת if/else if/else. בשרשרת כזאת רק ענף אחד יכול להתבצע: או הראשון, או השני, או השלישי, וכן הלאה.

if (grade >= 90)
{
    Console.WriteLine("excellent");
}
else if (grade >= 70)
{
    Console.WriteLine("good");
}
else
{
    Console.WriteLine("needs practice");
}

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

if (isCold)
{
    Console.WriteLine("take a sweater");
}

if (isHungry)
{
    Console.WriteLine("eat something");
}

if (isTired)
{
    Console.WriteLine("rest");
}

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

בלוקים וסוגריים מסולסלים

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

if (a < b)
    Console.WriteLine("a is smaller");

כתיבה בבחינה (שאני לא אכתוב בשיעור או בקוד)

if (a < b) Console.WriteLine("a is smaller");

כשקטגוריות חופפות: להתחיל מהמקרה הפרטי

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

if (isNewDriver && isYoungDriver)
    Console.WriteLine("new and young driver");
else if (isNewDriver)
    Console.WriteLine("new driver");
else if (isYoungDriver)
    Console.WriteLine("young driver");
else
    Console.WriteLine("regular driver");

אם נכתוב קודם את התנאי הכללי isNewDriver, המקרה המיוחד isNewDriver && isYoungDriver עלול לא להגיע בכלל לבדיקה.

$ במחרוזות: String Interpolation

שאלה שחזרה הייתה מתי משתמשים בסימן $. ב־C# סימן $ לפני מחרוזת מפעיל string interpolation: אפשר לשלב בתוך המחרוזת ערכים של משתנים או ביטויים. ההסבר מתחיל כאן.

int age = 16;
string name = "Dana";

Console.WriteLine($"{name} is {age} years old");

בלי $, הסוגריים המסולסלים הם סתם תווים בתוך מחרוזת:

Console.WriteLine("{name} is {age} years old");

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

double a = 3;
double b = 4;

Console.WriteLine($"a squared is {a * a}");
Console.WriteLine($"hypotenuse is {Math.Sqrt(a * a + b * b)}");

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

= לעומת ==

הבדל בסיסי אבל קריטי: = הוא השמה, ו־== הוא השוואה. השאלה הזו נענתה בחלק שלפני המעבר ללולאות.

color = "green";      // השמה: שמים ערך בתוך משתנה
color == "green";     // השוואה: האם הערך שווה ל־"green"?

בתוך תנאי צריך ביטוי בוליאני, כלומר משהו שהוא true או false:

if (color == "green")
{
    Console.WriteLine("go");
}

לכן == דומה מבחינה רעיונית ל־<, >, <=, >=: כולם יוצרים ביטוי לוגי שאפשר לשים בתוך if.

משולשים: לא מספיק לבדוק צלעות שוות

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

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

bool isIsosceles =
    a == b ||
    a == c ||
    b == c;

bool isRight =
    a * a + b * b == c * c ||
    a * a + c * c == b * b ||
    b * b + c * c == a * a;

if (isIsosceles && isRight)
{
    Console.WriteLine("isosceles right triangle");
}

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

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

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

דוגמה למקרה שצריך להפיל פתרון שבודק רק שתי צלעות שוות:

a = 5;
b = 5;
c = 8;

יש שתי צלעות שוות, אבל זה לא משולש ישר-זווית. לכן פתרון שמחזיר כאן “כן” הוא פתרון שגוי.

דיוק חישובי: לא תמיד משווים double עם ==

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

במקום:

a * a + b * b == c * c

לעיתים נעדיף לבדוק שההפרש קטן מסף דיוק מסוים:

const double EPS = 0.000001;

bool isRight =
    Math.Abs(a * a + b * b - c * c) < EPS ||
    Math.Abs(a * a + c * c - b * b) < EPS ||
    Math.Abs(b * b + c * c - a * a) < EPS;

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

סיכום קצר

  • שרשרת if/else if בוחרת ענף אחד; כמה if-ים נפרדים יכולים להפעיל כמה ענפים.
  • כשקטגוריות חופפות, מתחילים מהמקרה הספציפי.
  • $"..." מאפשר להכניס ביטויים לתוך מחרוזת בעזרת { ... }.
  • = הוא השמה; == הוא השוואה.
  • בדוגמות גאומטריות צריך לבדוק את כל תנאי השאלה, לא רק סימן חיצוני כמו “שתי צלעות שוות”.
  • מעבר טסטים לא מוכיח שהפתרון נכון אם הטסטים לא מכסים מקרי קצה.
  • עם double ושורשים, לעיתים בודקים קרבה לאפס בעזרת Math.Abs(...) < EPS.

להמשך חומר מסודר: פרק 3 - תנאים, ביטויים לוגיים ושארית חלוקה.