מדריך: לימוד שפת C

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

שימו ❤

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

אם זאת, הוא מוגבל ביכולותיו.

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

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

תודות:
על עורך הקוד ל ace.c9.io
על הקומפיילר ל rextester.com
על הסקריפטים ל jQuery.com
על הפונט ל fonts.google.com
ולכם קוראי המדריך

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

הקדמה

תכנית מחשב הינה אוסף של פקודות המיועדות לביצוע ע"י המחשב. קל יותר לתכנת ב"שפת תכנות עילית" (high-level programming language) משום שהיא משתמשת בניסוח ידידותי הדומה לשפת בני האדם. אם זאת, היא צריכה לעבור תהליך שבו מהדר (compiler) או מפרש (interpreter) יתרגם אותה לשפה שהמחשב מבין – שפת מכונה (machine language). "שפת תכנות נמוכה" (low-level programming language) אינה זקוקה לתיווך הזה, לכן הביצועים שלה טובים יותר, צריכת הזיכרון נמוכה יותר ומהירות הריצה גבוהה יותר, אך היא אינה ידידותית ולכן התכנות בה פחות נפוץ.

C היא שפת תכנות עילית, לכן, על אף שמכנים את אוסף הפקודות בשם "קוד", נדאג שהקוד שלנו יהיה קריא, מובן וידידותי. לשם כך נקפיד על מוסכמות הקידוד (coding conventions) הבאות:

הפעלת תכנית מחשב מכונה בשם "הרצה".

זיכרון

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

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

אנו רגילים לספור בבסיס בעל 10 ספרות (0 עד 9). נניח שאנו סופרים במספרים חיוביים שלמים. הספרה האחרונה היא 9, ולכן כשנרצה להמשיך הלאה, נשתמש ב 2 ספרות. נהפוך אותה ל 0 ונשים משמאלה 1 (כלומר 10). שוב נגדיל אותה כל פעם ב 1 ושוב כשהיא תגיע ל 9 נהפוך אותה ל 0 ונגדיל את ה 1 משמאלה ל 2 (כלומר 20). נמשיך בתהליך עד שהספרה משמאלה תגיע ל 9 (כלומר 99) ואז נעבור ל 3 ספרות וכן הלאה. לו היו לנו רק 2 ספרות, היינו סופרים כך: 0, 1, 10, 11, 100, 101, 110, 111, 1000 וכן הלאה… זיכרון ה RAM מורכב מיחידות. באנגלית מכנים כל יחידה כזו בשם binary digit ובקיצור Bit. בעברית מכנים אותה "ספרה בינארת" ובקיצור סיבית. סיבית כבויה מסומנת ב 0 וסיבית שזורם בה חשמל מסומנת ב 1. ברוב המחשבים, קבוצה המורכבת מ 8 סיביות נקראת "Byte". בעזרת רצף של סיביות ניתן לבטא מספר בבסיס 2. בסיס בעל 2 ספרות מכונה "בינארי". בסיס בעל 10 ספרות מכונה "דצימלי" ובעברית "עשרוני". למרות שהזיכרון מכיל ערכים בינאריים, נהוג לייצג את הכתובות של הזיכרון בבסיס בעל 16 ספרות (מ 0 עד 9 ומ A עד F) המכונה "הקסדצימלי". קיימות נוסחאות המאפשרות בקלות המרה של מספר כלשהו מבסיס כלשהו לבסיס כלשהו.

טיפוס

טיפוס גודל ב Byte טווח ערכים מינימלי
תלוי בגודל ה Byte שלרוב מורכב מ 8 סיביות (Bit)
פורמט
char 1
טיפוס זה בגודל יחידת הזיכרון הבסיסית של המחשב.
ניתן לייצג את הערך שהיא יכולה להכיל
ע"י תו (character) של ASCII
או ע"י מספר שלם (integer).
מ 128- עד 127 c%
signed char 1 מ 128- עד 127 ייצוג תווי c%
ייצוג מספרי hhi%
unsigned char 1 מ 0 עד 255 ייצוג תווי: c%
ייצוג מספרי: hhu%
short
short int
signed short
signed short int
2 מ 32768− עד 32767 hi%
unsigned short
unsigned short int
2 מ 0 עד 65535 hu%
int
signed
signed int
2 מ 32768− עד 32767 d% או i%
unsigned
unsigned int
2 מ 0 עד 65535 u%
long
long int
signed long
signed long int
4 ‎מ -2147483648 עד 2147483647‎ li%
unsigned long
unsigned long int
4 מ 0 עד 4294967295 lu%
long long
long long int
signed long long
signed long long int
8 מ 9223372036854775808- עד 9223372036854775807 lli%
unsigned long long
unsigned long long int
8 מ 0 עד 18446744073709551615 llu%
float התקן לא מגדיר התקן לא מגדיר ייצוג רגיל: %f או F%
ייצוג ע"י מנטיסה ואקספוננט: a% או A% או e% או E% או g% או G%
double התקן לא מגדיר התקן לא מגדיר ייצוג רגיל: %lf או lF%
ייצוג ע"י מנטיסה ואקספוננט: la% או lA% או le% או lE% או lg% או lG%
long double התקן לא מגדיר התקן לא מגדיר ייצוג רגיל: %Lf או LF%
ייצוג ע"י מנטיסה ואקספוננט: La% או LA% או Le% או LE% או Lg% או LG%

שימו ❤

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

רוב הזמן נשתמש ב:

  • char עבור תוים
  • int עבור שלמים עשרוניים
  • float עבור שברים עשרוניים
  • double עבור שברים עשרוניים ארוכים יותר

כמו כן, קיימים פורמטים רבים מעבר לאלה המובאים בעמודה השמאלית שבטבלה לעיל. את הרשימה המלאה ניתן למצוא בקישור הבא:
https://en.wikipedia.org/wiki/Printf_format_string
אך גם אותם אין צורך לזכור, אלא רק לדעת שהם קיימים ובמקרה הצורך ניתן להיעזר בקישור.

רוב הזמן נשתמש ב:

  • c% עבור char
  • d% או i% עבור int
  • f% עבור float
  • lf% עבור double
  • s% עבור מחרוזות
  • x% עבור בסיס הקסדצימלי עם אותיות קטנות
  • X% עבור בסיס הקסדצימלי עם אותיות גדולות
  • t\ בכדי לעבור לעמודה הבאה
  • n\ בכדי לעבור לשורה הבאה
  • \\ בכדי להציג \ (המכונה לוכסן הפוך, לוכסן שמאלי, backslash ובויקיפדיה בערך באנגלית מובאים לו עוד 11 שמות…)

אופרטור

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

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

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

a ו- b מסמנים אופרנדים.

אופרטור תאור דוגמא
אריתמטיים. התוצאה תוחזר במקום הביטוי המתמטי. לא יחול שינוי בערכי האופרנד שמשמאל והאופרנד שמימין לאופרטור
+ סכום האופרדנים שמשני צדי האופרטור a + b
האופרנד שמשמאל פחות האופרנד שמימין a – b
* מכפלת האופרנדים שמשני צדי האופרטור a * b
/ האופרנד שמשמאל חלקי האופרנד שימין a / b
% האופרנד שמשמאל מודולו האופרנד שמימין (השארית שמתקבלת מהאופרנד שמשמאל חלקי האופרנד שמימין. שני האופרנדים והשארית הינם שלמים) a % b
קיצורי דרך. התוצאה תוצב באופרנד שמשמאל לאופרטור
=+ כמו + אך התוצאה תוצב באופרנד שמשמאל a += b
=- כמו – אך התוצאה תוצב באופרנד שמשמאל a -= b
=* כמו * אך התוצאה תוצב באופרנד שמשמאל a *= b
=/ כמו / אך התוצאה תוצב באופרנד שמשמאל a /= b
=% כמו % אך התוצאה תוצב באופרנד שמשמאל a %= b
++ קבלת הערך של האופרנד שמשמאל ולאחר מכן הגדלתו ב 1 (לדוגמא, אם הערך 5 שמור באופרנד ובוצעה קריאה אליו עם אופרטור זה כדי להציגו על המסך, על המסך יופיע 5 ולאחר מכן הערך יוגדל ל 6) a++
קבלת הערך של האופרנד שמשמאל ולאחר מכן הקטנתו ב 1 (לדוגמא, אם הערך 5 שמור באופרנד ובוצעה קריאה אליו עם אופרטור זה כדי להציגו על המסך, על המסך יופיע 5 ולאחר מכן הערך יוקטן ל 4) a–
קיצורי דרך. התוצאה תוצב באופרנד שמימין לאופרטור
++ הגדלת הערך של האופרנד מימין ב 1 ולאחר מכן קבלתו (לדוגמא, אם הערך 5 שמור באופרנד ובוצעה קריאה אליו עם אופרטור זה כדי להציגו על המסך, הערך יוגדל ל 6 ולאחר מכן על המסך יופיע 6) ++a
הקטנת הערך של האופרנד מימין ב 1 ולאחר מכן קבלתו (לדוגמא, אם הערך 5 שמור באופרנד ובוצעה קריאה אליו עם אופרטור זה כדי להציגו על המסך, הערך יוקטן ל 4 ולאחר מכן על המסך יופיע 4) –a
יחסים. אם הביטוי "שקר" יוחזר 0 ואם הוא "אמת" יוחזר 1. התוצאה תוחזר במקום הביטוי המתמטי. לא יחול שינוי בערכי האופרנד שמשמאל והאופרנד שמימין לאופרטור
== אם האופרנדים שמשני צדי האופרטור שווים התוצאה תהיה אמת, אחרת שקר a == b
=! אם האופרנדים שמשני צדי האופרטור שונים התוצאה תהיה אמת, אחרת שקר a != b
> אם האופרנד שמשמאל לאופרטור קטן מהאופרנד שמימינו התוצאה תהיה אמת, אחרת שקר a < b
=> אם האופרנד שמשמאל לאופרטור קטן או שווה לאופרנד שמימינו התוצאה תהיה אמת, אחרת שקר a <= b
< אם האופרנד שמשמאל לאופרטור גדול מהאופרנד שמימינו התוצאה תהיה אמת, אחרת שקר a > b
=< אם האופרנד שמשמאל לאופרטור גדול או שווה לאופרנד שמימינו התוצאה תהיה אמת, אחרת שקר a >= b
לוגיים.
&& "וגם" – אם האופרנד שמשמאל לאופרטור אמת וגם האופרנד שמימין לאופרטור אמת, התוצאה תהיה אמת, אחרת שקר a == b
|| "או" – אם האופרנד שמשמאל לאופרטור אמת, או אם האופרנד שמימין לאופרטור אמת, או אם שניהם אמת, התוצאה תהיה אמת, אחרת שקר a || b
! "לא" – אם האופרנד שמימין לאופרטור שקר התוצאה תהיה אמת, אחרת שקר !a

שימו ❤

  • הערך 0 שווה גם לערך false (המייצג שקר), שווה גם לערך '0\' (המציין סוף מחרוזת) שווה גם לערך NULL (המציין מצביע שלא הוקצה עבורו זיכרון).
  • כל שאר הערכים המספריים שווים גם true (המייצג אמת).
  • באופרטור && אם האופרנד שמשמאל שקר האופרנד שמימין לא יבדק, עובדה שתעזור לנו בהמשך כשנלמד על מבני נתונים.
  • באופרטור || אם האופרנד שמשמאל אמת האופרנד שמימין לא יבדק, עובדה שתעזור לנו בהמשך כשנלמד על מבני נתונים.

פונקציה

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

הגדרת פונקציה:

קריאה לפונקציה:

שם פונקציה:

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

קליטת ערכים והחזרת ערך ע"י פונקציה:

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

פונקציה לא תבוצע אם לא היתה קריאה אליה.

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

include

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

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

  • הספריה stdio.h (קיצור של STanDard Input Output) מכילה פונקציות קלט כמו: scanf ופלט כמו: printf.
  • הספריה stdlib (קיצור של STanDard LIBrary) מכילה פונקציות מתמטיות כמו: abs, div, פונקציות של המרת מספר למחרוזת ולהפך, פונקציות של הגרלת מספר, פונקציות של מיון מהיר וחיפוש בינארי, פונקציות של יצירת קשר עם שורת הפקודה (CMD), פונקציות של הקצאת ושחרור זיכרון ועוד.
  • הספריה math.h מכילה עוד הרבה פונקציות מתמטיות, המקבלות ערכים, מבצעות עליהם פעולות מתמטיות ומחזירות את התוצאות, כמו: cos, sin, exp, fmod, log, pow, sqrt.

כאמור, הפונקציה printf נמצאת בספריה stdio.h, מקבלת מחרוזת ומציגה אותה על המסך.

כדי לקשר את הספריה stdio.h לתכנית שלנו נשתמש בפקודה include.

נכתוב את הפונקציה main. במקרה הזה, לא חשוב לנו ש main תחזיר ערך, לכן היינו יכולים לכתוב לפניה void, אך כדי שהתכנית שלנו תוכל לרוץ בכל הקומפיילרים, נצמד לתקן, שמחייב לפני main לכתוב int. הטיפוס int מייצג ערך של מספר שלם. בהמשך נרחיב על הטיפוסים השונים. כמו כן, התקן מאפשר לנו ש main גם תקבל ערכים משורת הפקודה (CMD) באופן הבא:
(int main (int argc, char **argv
אך זאת הוא אינו מחייב ולכן בשלב זה נשאיר את הסוגריים ריקות. מכיוון שהפונקציה חייבת להחזיר ערך שלם, נחזיר את הערך 0 ע"י הפקודה return. מכיוון שכאשר הפקודה return מופעלת מתבצעת יציאה מהפונקציה, כל מה שאנו רוצים לבצע בפונקציה נכתוב לפני ה return. מה שנרצה לבצע זה, לקרוא לפונקציה printf ולשלוח לה את המחרוזת Hello World כדי שהיא תוצג על המסך. מחרוזת יש להגדיר בתוך גרשיים. אחרי כל פקודה יש לכתוב את האופרטור ;


הערה

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

התכנית הבאה מתעלמת מההערות ומציגה את המחרוזת Hello World:


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

שימו ❤

  • תו בודד יש להגדיר בתוך שני גרשים – לדוגמא התו a.
  • מחרוזת (רצף של תוים) יש להגדיר בתוך שני גרשיים – לדוגמא המחרוזת ש printf מקבלת.
  • גם אם תו מכיל ספרה או כל המחרוזת מכילה מספר, לא ניתן להתייחס אליהם כספרה או כמספר לצורך חישוב מתמטי.

דוגמא:


בדוגמא הבאה נציג את המספר 90 בכמה פורמטים שונים:


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

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


נראה מספר דוגמאות בהן המרה יכולה לסייע לנו:

אם ננסה להריץ ;(printf ("%d\n", 0.1 הקומפיילר יציג לנו אזהרה ופלט בלתי צפוי, מפני שהגדרנו את הערך כשבר והפורמט d% משמש להצגת שלם.
זה יקרה גם בכיוון הפוך. אם ננסה להריץ ;(printf ("%f\n", 1 הקומפיילר יציג לנו אזהרה ופלט בלתי צפוי, מפני שהגדרנו את הערך כשלם והפורמט f% משמש להצגת שבר.
זה יקרה אפילו אם ננסה להריץ ;(printf ("%f\n", 1/2 למרות שכביכול הגדרנו שבר, מפני שהמחשב עדיין מזהה את הספרות כשלמים ולכן הפעולה 1/2 מחזירה תוצאה שלמה.

שימו ❤

  • בניגוד לשגיאות שאינן מאפשרות להריץ את התכנית, אזהרות מאפשרות, אך מתריעות מפני כתיבה לא תקנית העלולה להביא לתוצאות בלתי רצויות, כמו איבוד נתונים. אסור לזלזל באזהרות. הן עשויות להיות חמורות אף יותר משגיאות!
  • המרת שלם כמו 1 לשבר הופכת אותו ל 1.000000
  • המרת שבר כמו 1.999999 לשלם הופכת אותו ל 1, כלומר המחשב משמיט את הספרות שאחרי הנקודה ולא מעגל לשלם הקרוב (2).

התכנית הבאה מפרטת אלו צורות כתיבה אינן נכונות ואלו נכונות:


בעיית דיוק של יצוג ערך עם נקודה צפה (floating point) בבסיס בינארי

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

בין ה % לפורמט f או lf אפשר לקבוע כמה ספרות יופיעו אחרי הנקודה העשרונית.

שימו ❤

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


משתנה וקבוע

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

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

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

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

כמו כן, אם נרצה, נוכל לשנות את הערך השמור בכתובת לערך אחר. לכן לשם שאנו נותנים לכתובת קוראים "משתנה".

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

כללים ליצירת משתנה או קבוע:

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

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


תרגילי חזרה בסיסיים

ממשו את הפונקציות הבאות, המקבלות רדיוס של כדור:
1. (double ball_circumference (double ball_radius מחזירה את ההיקף שלו, על פי הנוסחה: 2πr
2. (double ball_surface_area (double ball_radius מחזירה את שטח הפנים שלו, על פי הנוסחה: 4πr2
3. (double ball_volume (double ball_radius מחזירה את הנפח שלו, על פי הנוסחה: 43πr3
הפונקציה main תבקש מהמשתמש להזין את הרדיוס, תשלח את הרדיוס לשלוש הפונקציות ותציג את הפלט שלהן

דוגמת הרצה:
1.2 :Enter ball radius
circumference = 7.539822
surface area = 18.095574
volume = 7.238229

עזרה:
את הפעולה של חזקה, לדוגמא r2, ניתן לבצע כך: r * r או באמצעות הוספת הספריה math.h בה מוגדרת הפונקציה pow שמקבלת בארגומנט הראשון את הבסיס ובשני את החזקה ומחזירה את התוצאה.

שימו ❤ לחישובי שברים:
הפלט של: ;(printf ("%lf", 1 / 2 יהיה אפס ולא חצי. משום שהתכנית מזהה שהמספרים 2 ,1 הם int ולכן מחזירה תוצאה int ומשמיטה את כל מה שאחרי הנקודה, וזאת על אף שהפורמט של הפלט %lf מצפה למספר ממשי!
כדי לקבל פלט ממשי, צריך שלפחות ספרה אחת לא תהיה int, לדוגמא: ;(printf ("%lf", 1.0 / 2 או שנמיר את המספר לממשי, לדוגמא: ;(printf ("%lf", (double) 1 / 2.

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


תרגילי חזרה בתנאים

ממשו את הפונקציות הבאה:
(void sort3nums (double num1, double num2, double num3
המקבלת 3 מספרים ממשיים ומציגה אותם מהקטן לגדול (יתכן שכל המספרים שונים, או ש 2 או ש 3 זהים).
לדוגמא קלט: 1- ,1.2 ,1.2, פלט: 1.2 ,1.2 ,1-

את הבדיקות יש לבצע באמצעות if, else בלבד.

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


ממשו את הפונקציות הבאה:
(void change (double money
המקבלת מספר ממשי המייצג סכום כספי ומציגה את הכסף פרוט להכי מעט שטרות ומטבעות, כלומר עדיפות ל 200, אח"כ 100, אח"כ 20, 10, 5, 2, 1, 0.5, 0.1.
אחרי הנקודה יכולה לבוא רק ספרה אחת, המייצגת את כמות עשרות האגורות. אם אחריה ישנה ספרה נוספת ניתן להשמיט אותה.
בין הפירוט של כל שטרות ומטבעות יופיע + (אך לא יופיע + מיותר).
במידה ואותו השטר או המטבע מופיעים יותר מפעם אחת (יתכן בשטרות של 200, 20 ובמטבעות של 0.2, 0.1) תופיע הכמות כפול השטר או המטבע (אך אם השטר או המטבע מופיעים רק פעם אחת אין לשים לפניהם "* 1").

את הבדיקות יש לבצע באמצעות if, else בלבד.

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

דוגמאות:
קלט: 544.20, פלט: 0.1 * 2 + 2 * 2 + 20 * 2 + 100 + 200 * 2 = 544.20
קלט: 383.60, פלט: 0.1 + 0.5 + 1 + 2 + 10 + 20 + 50 + 100 + 200 = 383.60

עזרה:
לדוגמא, נניח ש money מכיל את הכסף והוא גדול מ 200.
כדי להציג כמה שטרות של 200 ישנם, נצטרך לחלק את הכסף השלם (כלומר ללא האגורות) ב 200. כדי להיפטר מהאגורות נמיר את הסכום לסכום שלם ע"י הוספת "(int)" כלומר: int) money / 200).
לאחר מכן נוריד את מה שהצגנו מהסכום: money -= (int) money / 200 * 200.
נמשיך באותה הדרך על הסכום שנשאר.
ישנם שטרות ומטבעות שיכולים להופיע רק פעם אחת. לדוגמא 100, כי אם לדוגמא הסכום 500, יופיעו 2 שטרות של 200 ורק שטר אחד של 100.
לפי אותו הרעיון ישנם שטרות ומטבעות שיכולים להופיע לכל היותר פעמיים ומטבע שיכולה להופיע לכל היותר 4 פעמים.


ממשו את הפונקציה הבאה:
(void SumOfDays (int year, int month
כתבו פונקציה המקבלת שנה וחודש ומציגה כמה ימים מכיל החודש ע"פ הכלל הבא:
חודש פברואר מכיל 28 ימים, או 29 ימים כאשר: השנה מתחלקת ללא שארית ב 4 אבל לא ב 100, או כשהיא מתחלקת ללא שארית ב 400.
החודשים אפריל, יוני, ספטמבר ונובמבר מכילים 30 ימים.
שאר החודשים מכילים 31 ימים.

את הבדיקות יש לבצע באמצעות switch case בלבד.


ממשו את הפונקציה הבאה:
(int SumOfDays (int year, int month
הפועלת כמו הפונקציה הקודמת, אך אינה מציגה אלא מחזירה את מספר הימים.
הפעם אין להשתמש ב break אלא ב return

מצורפת תכנית בדיקה הנעזרת בפונקציה המשתמשת ב if בלבד ובדקת בני כמה אתם ומתי יהיה יום הולדתכם הבא.
יש להזין את תאריך הולדתכם בפורמט DD/MM/YYYY

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


לולאות – תרגילים

1. ממשו את הפונקציה (int fib (int num
המקבלת אינדקס ומחזירה בהתאם את ערך האיבר שבמיקום זה בסדרת פיבונאצ'י.

פתרון:


2. ממשו את הפונקציה (int isPrime (int num
המקבלת מספר, מחזירה 1 אם הוא ראשוני ו- 0 אם לא.

פתרון:


3. ממשו את הפונקציה (void rectangle (int rows, int cols
המקבלת כמות שורות וכמות עמודות ומציגה בהתאם מלבן (מרובע שבו כל הזוויות ישרות) מכוכביות, לדוגמא:

*****
*****

פתרון:


4. א. ממשו את הפונקציה (void square (int size
המקבלת גודל (שורות ועמודות) ומציגה בהתאם ריבוע (מלבן שבו כל הצלעות באורך שווה) מכוכביות, לדוגמא:

***
***
***

פתרון:


4. ב. ממשו את הפונקציה (void multiplicationTable (int size
המקבלת גודל (שורות ועמודות) ומציגה בהתאם את לוח הכפל.

פתרון:


5. ממשו את הפונקציה (void triangle1 (int size
המקבלת גודל ומציגה בהתאם משולש כסף (ישר זווית ושווה שוקיים) מכוכביות, כשהזוית הישרה נמצאת בפינה השמאלית התחתונה (בהנחה שהפלט מוצמד לשמאל ונכתב משמאל לימין), לדוגמא:

***
***
***

פתרון:


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

***
***
***

פתרון:


7. א. ממשו את הפונקציה (void pyramid (int size
המקבלת גודל ומציגה בהתאם פירמידה מכוכביות (בהנחה שהגופן ברוחב אחיד – monospace כך שתו של רווח ותו של כוכבית באותו הרוחב), לדוגמא:

***
****
*****

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


פתרון באמצעות 3 לולאות – הצגת הכוכביות באמצעות לולאה אחת, בהנחה כי בכל שורה מספר הכוכביות שווה למספר השורה פי 2 פחות 1:


פתרון באמצעות 3 לולאות – הצגת הכוכביות באמצעות לולאה אחת, בהנחה כי בשורה הראשונה יש כוכבית אחת, ובכל שורה מספר הכוכביות גדל ב 2:


פתרון באמצעות 2 לולאות ותנאי – הצגת הרווח והכוכביות באמצעות לולאה אחת:


7. ב. ממשו את הפונקציה (void pyramid (int size
המקבלת גודל ומציגה בהתאם פירמידה מספרות, כך שהעמודה האמצעית מורכבת מספרות של 0 וככל שהעמודות מתרחקות ממנה הן הולכות וגדלות, לדוגמא:

000
1010
21012

פתרון באמצעות 4 לולאות, בשיטת המשולשים שפתרנו בשאלות הקודמות:


פתרון באמצעות 3 לולאות:


7. ג. ממשו את הפונקציה (void pyramid (int size
המקבלת גודל ומציגה בהתאם פירמידה מספרות באופן הפוך, כך שהעמודות החיצוניות מתחילות מ 0 וככול שהן מתקרבות לעמודה האמצעית הן הולכות וגדלות, לדוגמא:

000
0100
01210

פתרון:


8. א. ממשו את הפונקציה (void circle (double height, double width, double x, double y, double r, double thick
המקבלת גובה ורוחב של דף ציור, שיעורי נקודה של מרכז מעגל, רדיוס ועובי, ומציגה בהתאם מעגל מכוכביות.

עזרה:

  • כדי לדעת איזה תו להציג באיזו משבצת (רווח או כוכבית), נבצע מעבר על כל משבצות דף הציור ע"י לולאה בתוך לולאה (כפי שעשינו במלבן: לולאה חיצונית שתעבור על כל שורות הדף ובתוכה לולאה פנימית שתעבור בכל שורה על כל העמודות שלה).
  • בכל משבצת נמדוד את המרחק שלה ממרכז המעגל, באמצעות נוסחת מרחק בין 2 נקודות: \(distance = \sqrt{{(x_1 – x_2)}^2 + {(y_1 – y_2)}^2}\)
  • יש לקחת בחשבון גם את עובי הרדיוס, לכן נחלק אותו ב 2. אם המרחק נמצא בטווח שבין הרדיוס פחות מחצית העובי לבין הרדיוס ועוד מחצית העובי, יש להציג כוכבית, אחרת יש להציג רווח.

פתרון:


8. ב. ממשו את הפונקציה (void circle (double height, double width, double x, double y, double r, double thick
המקבלת גובה ורוחב של דף ציור, שיעורי נקודה של מרכז מעגל, רדיוס ועובי, ומציגה בהתאם מעגל מספרות, כך שההיקף החיצוני של המעגל מתחיל מספרות של 0 וככל שמתקדמים פנימה הן הולכות וגדלות.

פתרון:


8. ג. ממשו את הפונקציה (void circle (double height, double width, double x, double y, double r, double thick
המקבלת גובה ורוחב של דף ציור, שיעורי נקודה של מרכז מעגל, רדיוס ועובי, ומציגה בהתאם מעגל מספרות כך שההיקף הפנימי של המעגל מתחיל מספרות של 0 וככל שמתקדמים החוצה הן הולכות וגדלות.

פתרון:


9. ממשו את הפונקציה (void threeDigits (int base
המקבלת בסיס ומציגה את כל המספרים בעלי 3 ספרות, מ 000 עד המספר המקסימלי בעל 3 ספרות בבסיס שהיא קבלה. לדוגמא, אם הבסיס הוא בינארי, המספר האחרון יהיה 111 ואם הבסיס הוא הקסדצימלי, המספר האחרון יהיה FFF.

פתרון:


10. ממשו את הפונקציה (void natural3nums (int num
המקבלת מספר (ניתן להניח שהוא טבעי וגדול מ 5) ומציגה את כל האפשרויות לקבל אותו כסכום של שלשה מספרים טבעיים גדולים מ 0,
ללא חזרה על מספר באותה האפשרות (כגון 2 + 1 + 1),
ללא חזרה על אפשרות בסדר אחר (כגון האפשרות 3 + 2 + 1 והאפשרות 2 + 3 + 1).
לדוגמא, אם הפונקציה מקבלת: 13, הפלט יוצג כך:

10 + 2 + 1
9 + 3 + 1
8 + 4 + 1
7 + 5 + 1
8 + 3 + 2
7 + 4 + 2
6 + 5 + 2
6 + 4 + 3

פתרון:


11. ממשו את הפונקציה (double sinus (double x
המקבלת מספר ממשי ומחזירה את ערך הסינוס שלו, ללא שימוש בפונקציות הטריגונומטריה המובנות. לדוגמא, אם הפונקציה מקבלת: 45, יוחזר הערך: 0.850904.

עזרה:

  • יש לוודא שהמספר x בין 2π- ל 2π. אם הוא קטן מהתחום, יש להוסיף לו 2π בעזרת לולאה שוב ושוב עד שיהיה בתחום. אם הוא גדול מהתחום, יש להפחית ממנו 2π- בעזרת לולאה שוב ושוב עד שיהיה בתחום.
  • אחרי שהמספר x בתחום, בעזרת שתי לולאות בלבד. חשבת את הערך בעזרת נוסחת טור טיילור הבאה: \(\sin x=\ {\frac {x^{1}}{1!}}-{\frac {x^{3}}{3!}}+{\frac {x^{5}}{5!}}-{\frac {x^{7}}{7!}}+\cdots =\sum _{n=0}^{\infty }{\frac {(-1)^{n}}{(2n+1)!}}x^{2n+1}\)
    בלולאה הפנימית נחשב סכום כל שבר בנפרד ובחיצונית את סכום כל השברים.

פתרון:


פוינטרים


מערכים



רשימה מקושרת דו כיוונית


מחסנית


תור

#include < stdio.h >
#include < stdlib.h >

typedef struct queue
{
	int *arr, // מערך לאכסון הערכים
		size, // (גודל התור (המערך
		put, // מיקום אליו ידחף הערך הבא
		take, // מיקום ממנו ישלף הערך הבא
		count_vals; // מונה את מספר הערכים שהתור מכיל
} queue;

// אתחול תור
void new_queue (queue *q, int size)
{
	if (size < 0)
		q->arr = NULL;
	else
	{
		q->arr = (int*) malloc (sizeof (int) * size);
		q->size = size;
		q->put = q->take = q->count_vals = 0;
	}
}

// שחרור מערך שהוקצה לתור
void free_queue (queue *q)
{
	free (q->arr);
}

// האם תור מלא
int is_full_queue (queue q)
{
	return q.count_vals == q.size;
}

// האם תור ריק
int is_empty_queue (queue q)
{
	return q.count_vals == 0;
}

// כמות הערכים בתור
int count_queue_vals (queue q)
{
	return q.count_vals;
}

// דחיפת ערך לתור
void push_queue (queue *q, int val)
{
	if (q->arr != NULL && !is_full_queue (*q))
	{
		q->arr[q->put] = val;
		q->put = (q->put + 1) % q->size;
		q->count_vals++;
	}
}

// שליפת ערך מתור
void pop_queue (queue *q)
{
	if (q->arr != NULL && !is_empty_queue (*q))
	{
		q->take = (q->take + 1) % q->size;
		q->count_vals--;
	}
}

// קבלת ערך מתור
int top_queue (queue q)
{
	if (q.arr != NULL && !is_empty_queue (q))
		return q.arr[q.take];
	return -1;
}

// הצגת כל ערכי תור
void show_queue (queue q)
{
	while (!is_empty_queue (q))
	{
		printf ("%d ", top_queue (q));
		pop_queue (&q);
	}
}

// בדיקה
void main ()
{
	queue q;
	
	// בדיקת מקרה קצה של הקצאה בגודל שלילי
	// q->arr = NULL תאתחל new_queue הפונקציה
	// אחרת כל שאר הפונקציות יקרסו
	new_queue (&q, -1); 
	pop_queue (&q);
	push_queue (&q, 10);
	printf ("%d ", top_queue (q));
	free_queue (&q);

	// בדיקת מקרה קצה של הקצאה בגודל חיובי
	new_queue (&q, 1);
	push_queue (&q, 10);
	push_queue (&q, 20); // דחיפה לתור מלא
	pop_queue (&q);
	pop_queue (&q); // שליפה מתור ריק
	printf ("%d\n", top_queue (q)); // קבלת ערך מתור ריק
	free_queue (&q);

	// בדיקה
	new_queue (&q, 3);
	push_queue (&q, 10);
	push_queue (&q, 20);
	push_queue (&q, 30);
	pop_queue (&q);
	push_queue (&q, 40);
	pop_queue (&q);
	pop_queue (&q);
	pop_queue (&q);
	push_queue (&q, 50);
	push_queue (&q, 60);
	push_queue (&q, 70);
	show_queue (q);
	free_queue (&q);
}

שאלות:

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

1. בשאלה זו אסור להשתמש בשדה count_vals.
יש להגדיר את הנתונים הבאים כשדות (במקום כפונקציות) is_empty, is_full ולעדכנם בפונקציות הרלוונטיות.
בהתאם לכללים הללו, הגדירו מחדש את התור ואת הפונקציות הבאות: new_queue, free_queue, push_queue, pop_queue, top_queue, show_queue, count_vals_queue







כתיבת תגובה


www.000webhost.com