Smart-World Surf

יחידה 2: יסודות C: אופרטורים ובקרת זרימה

ביטויים, אופרטורים לוגיים ואריתמטיים, משפטי תנאי ולולאות.
אופרטורים (חשבונייםיחסייםלוגייםביט-ביט)משפטי תנאי (ifelse ifelseswitch)לולאות (forwhiledo-while)המרות טיפוסים

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

אופרטורים ב-C: הכלים לטיפול בנתונים

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

אופרטורים חשבוניים

מבצעים פעולות מתמטיות בסיסיות: חיבור (+), חיסור (-), כפל (*), חילוק (/), שארית (%). כוללים גם אופרטורי הגדלה (++) והקטנה (--) בקדם (prefix) או באחור (postfix).

אופרטורים יחסיים

משווים בין שני אופרנדים ומחזירים ערך בוליאני (אמת/שקר, ב-C מיוצג ע"י 1/0). שווה (==), שונה (!=), קטן מ-(<), גדול מ-(>), קטן או שווה (<=), גדול או שווה (>=).

אופרטורים לוגיים

משלבים ביטויים בוליאניים ומחזירים ערך בוליאני. וגם לוגי (&&), או לוגי (||), שלילה לוגית (!). חשוב להבין את עקרון "קיצור המעגל" (short-circuit evaluation).

אופרטורים ביט-ביט

מבצעים פעולות על רמת הסיביות של מספרים שלמים. וביט-ביט (&), או ביט-ביט (|), XOR ביט-ביט (^), היפוך סיביות (~), הזזה שמאלה (<<), הזזה ימינה (>>).

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

בקרת זרימה: משפטי תנאי

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

if, else if, else

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

switch

מתאים למצבים בהם יש לבחור מבין מספר ערכים בדידים של משתנה (שלם או תו). כל case מייצג ערך אפשרי, וחשוב להשתמש ב-break כדי למנוע "נפילה" (fall-through) ל-case הבא. default מטפל במקרים שלא תואמים לאף case.

ביטוי בוליאני (Boolean Expression): ביטוי שתוצאתו היא אמת (ערך שאינו אפס) או שקר (אפס). משמש כתנאי במשפטי if ובלולאות.

בקרת זרימה: לולאות

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

for

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

while

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

do-while

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

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

המרות טיפוסים (Type Conversions)

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

המרת טיפוסים מפורשת (Explicit Type Conversion / Casting): מתבצעת באופן יזום על ידי המתכנת באמצעות אופרטור ה-cast (type)expression. לדוגמה: (double)sum / count.
המרת טיפוסים מרומזת (Implicit Type Conversion / Coercion): מתבצעת אוטומטית על ידי המהדר במצבים מסוימים, למשל בעת השמה של ערך מטיפוס אחד למשתנה מטיפוס אחר, או בביטויים מעורבים.
המרות טיפוסים מרומזות ואיבוד מידע: במקרים רבים, המרות מרומזות מטיפוס "רחב" לטיפוס "צר" (לדוגמה, מ-double ל-int) יגרמו לאיבוד מידע (כמו החלק השברי). כמו כן, חילוק של שני מספרים שלמים ב-C תמיד יחזיר מספר שלם, גם אם התוצאה האמיתית היא שברית. לדוגמה, 5 / 2 ייתן 2, לא 2.5. כדי לקבל תוצאה מדויקת, יש לבצע המרה מפורשת לאחד האופרנדים: (double)5 / 2. שימו לב גם לבעיות שעלולות לצוץ בהשוואות בין טיפוסים עם סימן (signed) וללא סימן (unsigned).

שאלות לדיון

  • מתי תעדיפו להשתמש במבנה if-else if-else על פני switch, ולהיפך? תנו דוגמה לכל מקרה.
  • הסבירו את ההבדל העיקרי בין לולאת while ללולאת do-while. באיזה תרחיש לולאת do-while תהיה עדיפה?
  • מהי קדימות אופרטורים ומדוע היא כה חשובה בתכנות C? תנו דוגמה לביטוי שבו אי הבנה של קדימות תוביל לתוצאה שגויה.
  • תארו תרחיש שבו המרת טיפוסים מרומזת עלולה להוביל לתוצאות בלתי צפויות או לשגיאות לוגיות. כיצד ניתן למנוע זאת באמצעות המרה מפורשת?

נקודות לתשובת מודל

  • if-else if-else מול switch: if-else גמיש יותר, מתאים לבדיקת טווחים, ביטויים לוגיים מורכבים או טיפוסי נתונים שאינם שלמים. switch יעיל וקריא יותר לבדיקת ערכים בדידים של משתנה שלם/תו, אך דורש break למניעת fall-through.
  • while מול do-while: while בודק את התנאי לפני ביצוע הלולאה (0 או יותר איטרציות). do-while מבצע את הלולאה לפחות פעם אחת לפני בדיקת התנאי (1 או יותר איטרציות). do-while עדיף כאשר אנו רוצים להבטיח ביצוע ראשוני, למשל בקריאת קלט מהמשתמש שחייב להתבצע לפחות פעם אחת.
  • קדימות אופרטורים: קובעת את סדר הפעולות בביטוי. חיונית לקבלת תוצאות נכונות. לדוגמה, בביטוי a + b * c, הכפל יתבצע לפני החיבור. אי הבנה עלולה לגרום ל-(a + b) * c במקום a + (b * c).
  • בעיות בהמרות מרומזות: חילוק שלמים (int / int) תמיד יקצץ את החלק השברי. לדוגמה, int x = 7; int y = 3; double result = x / y; ייתן 2.0 ולא 2.333.... כדי לתקן: double result = (double)x / y;. בעיות נוספות עלולות לצוץ בהשוואות בין signed ל-unsigned, כאשר ערכים שליליים ב-signed הופכים לערכים חיוביים גדולים ב-unsigned.
מצאתם טעות או שחסר משהו?
→ הקודמת
מבוא לתכנות C וסביבת פיתוח
הבאה ←
פונקציות ומבנה תוכנית