בסיס לשפת C: נכתב ע"י Crossbow
שפת C היא אחת מן השפות המשומשות ביותר בעולם כיום. שפת C פותחה על ידי דניס ריצ'י ליצירת מערכת
ההפעלה יוניקס, וכיום יש לה מהדרים רבים, תקן סטנדרטי שנקרא Ansi-C, ומאפיינים שונים. שפת C היא שפה
עילית פרוצדורלית. המהדרים הידועים ביותר לשפת C הם Gnu C Compiler ו- Boarland Turbo C. במאמר זה
נסקור את התחביר של שפת C, את מיטב השימוש בשפה ועוד...
בסיס שפת C
התוכנית הראשונה שלנו תהיה תוכנית שתדפיס פלט סטנדרטי למסך. הפלט יהיה קבוע לתוכנית. כל תוכנית
מתחילה בפונקצייה שנקראת main. פונקצייה זו היא למעשה הכרזה על גוף התוכנית, כלומר, התוכנית תבצע את
הפקודות הכתובות שם בלבד. מלבד פונקציית main, נזדקק לשימוש בספרייה הסטנדרטית של שפת C. אנו נשתמש
בספריות שבהן כתובות פונקציות, פרוצדורות והגדרות מסויימות שישמשו את התוכניות שלנו, בעזרת הנחייה
שנקראת include. כעת נלמד את הרעיונות של השפה באופן כללי:
1) בשפת C, יש הבדל בין אותיות גדולות לקטנות, כלומר Main לא תפורש כ- main.
2) בשפת C, הנחייה נכתבת אך ורק לאחר סימן סולמית #, ותופיע בדרך כלל בתחילת התוכנית עצמה, לפני
פונקציית main.
3) בשפת C, סוף כל פקודה מסתיימת בסימן של נקודה פסיק ;.
4) בשפת C ישנן מילים שמורות, כגון int, char, void, float ועוד. המילים הללו שמורות למהדר, כלומר,
אסור להשתמש בהן כשם של פונקצייה למשל.
5) תחילת כל בלוק (נדון על כך בהמשך) יסומן בסימן }, וסוף בלוק בסימן {.
אלו הם הכללים העיקריים של השפה. כעת נוכל לכתוב את התוכנית הראשונה שלנו. הדבר הראשון שנעשה הוא
הכללת הספרייה הסטנדרטית של שפת C בתוך התוכנית על ידי שימוש בהנחייה include. שמה של הספרייה יופיע
בין סוגריים משולשות. שם הספרייה הסטנדרטית שלנו היא stdio.h. ספרייה מקבלת בדרך כלל את הסיומת h,
שפירושה הוא header. כעת, נגדיר את התחלת התוכנית באמצעות פונקציית main, ופתיחת בלוק אחריה. בתוך
הבלוק, נכתוב פקודה שנקראת printf. פקודה זו מקבלת כארגומנט מחרוזת (טקסט), ופולטת אותו אל המסך.
מכיוון שפקודה זו מקבלת ערך אך לא מחזירה ערך, נגדיר אותה כפרוצדורה. פרוצדורה היא פקודה המסוגלת
לקבל ערכים מסויימים, אך לא מחזירה אף ערך. בין סוגריים רגילות נכתוב את הטקסט שנרצה, אך מכיוון
שזהו טקסט קבוע לתוכנית, נכתוב אותו בין גרשיים. לא נשכח לסגור את הבלוק לבסוף, והתוצאה שנקבל היא
התוכנית שלהלן:
#include <stdio.h>
void main(void)
{
printf("This is my first C program!");
}
כעת נוכל לראות, שגם פונקציית main היא למעשה פרוצדורה. למעשה, main יכולה להיות
מוגדרת בדרכים שונות, אך הפעם הגדרנו אותה בתור void. המילה השמורה void למעשה
מתעלמת מסוג של ערך. כפי שנווכח בהמשך, למשתנים ולפונקציות בשפת C יכולים להיות
סוגים רבים של ערכים. void מתעלמת מסוג הערך. כעת, על מנת שנוכל לעשות את הדברים
מעניינים יותר במקצת, נוסיף מרכיב חדש לתוכנית שלנו. במקום לכתוב את הקבוע של
הטקסט בתוך הפקודה printf, נכתוב אותו בתוכנית כקבוע. קבוע (Constant) הוא חלק
בתוכנית שמקבל שם וערך קבוע. ערכו אף פעם לא משתנה. לכך עולה השאלה: למה לעשות
שימוש בקבועים? נניח שאנו רוצים להדפיס את הטקסט שהדפסנו מקודם שלוש פעמים. אנו
יכולים לכתוב באמת שלוש פעמים את הפקודה printf יחד עם הארגומנט הטקסטואלי, אך
נוכל במקום זאת לתת לטקסט שם קצר, וכל פעם שנכתוב בתוכנית את השם הזה, התוכנית
תחליף אותו בערך שנקבע לו. ההגדרה על קבוע היא define, וזוהי הנחייה. שמו של
הקבוע לא יכול להיות מילה שמורה, וכמו כן חייב להתחיל באות אנגלית, ולא יכול
להכיל תווים מיוחדים, כמו למשל סימן שאלה, סימן קריאה, סולמית וכדומה. להלן
התוכנית שלנו כעת עם הקבוע:
#include <stdio.h>
#define txt "This is my first C program!"
void main(void)
{
printf(txt);
printf(txt);
}
מי שניסה להריץ את התוכנית, בוודאי קיבל את הטקסט שרצינו להדפיס פעמיים, אך צצה
לנו בעייה: הטקסט השני יודפס בסמוך לטקסט הראשון, ללא רווחים וללא ירידת שורה.
נפתור בעייה זו בעזרת קבוע פשוט ביותר: כל פעם שנרצה לכתוב שורה, נכניס לתוך
הטקסט עצמו n\. סימן זה מאלץ את הפרוצדורה printf לרדת שורה. לכן, הקבוע שלנו
יוגדר בתור טקסט ובסופו סימן ירידת השורה. להלן התוכנית:
#include <stdio.h>
#define txt "This is my first C program!\n"
void main(void)
{
printf(txt);
printf(txt);
}
\* סימנים מסוג זה נקראים תווי בריחה,כמה תווי בריחה אחרים:
\n קפיצת שורה
\b מכניס רווח
\t רווח של טאב
\a עושה ביפ בסוף הכתיבה
\' כדי להכניס ' למחרוזת
\" כדי להכניס " למחרוזת
*\
כעת נתחיל להשתמש במשתנים. משתנה (Variable) הוא חלק בתוכנית שמקבל שם וערך. סוג
המשתנה נקבע בתחילת התוכנית. בשפת C, ההבדלים העיקריים בין סוגי המשתנים הם לא
הייעוד שלהם (כמו טקסט או מספר), אלא המקום שהם תופסים בזכרון. המשתנה מהסוג הכי
קטן הוא char, שבאופן עקרוני משמש לשמירת תווים. משתנה נוסף הוא int, שבאופן
עקרוני משמש לשמירת ערכים מספריים, יש void שעליו כבר דיברנו ויש float שמשמש
לשמירת ערכים מספריים ממשיים. אלו הם סוגי המשתנים שנלמד להשתמש בהם במאמר זה.
נתחיל ממשתנה char. נוכל לכתוב תוכנית שתדפיס טקסט מסויים, ואז תקלוט מספר כקלט
סטנדרטי מהמשתמש, ותציג את המספר כפלט. נשמע מסובך? לא כל כך:
#include <stdio.h>
void main(void)
{
char ch=0;
printf("Please enter a number:\n");
scanf("%d",&ch);
printf("\nYou've entered the number %d",ch);
}
טוב, יש כאן הרבה דברים להסביר. לאחר פתיחת התוכנית, הכרזנו על משתנה חדש מסוג char שנקרא ch. כמו
כן, הצבנו לו ערך התחלתי: אפס. הצבת ערך התחלתי היא לא חובה, אך רצוי בדרך כלל להציב ערך התחלתי
למספר. לאחר מכן הדפסנו טקסט המבקש מהמשתמש להכניס ערך מספרי. לאחר מכן, נקלוט את הערך מהמשתמש על
ידי פרוצדורת scanf. זוהי פרוצדורה המקבלת כארגומנט את סוג הקליטה ואת שם המשתמש. הסימן %d מסמן
לפרוצדורה כי אנו נקלוט ערך מספרי. ישנם עוד סימנים, כמו %c לתווים, %s לטקסט ארוך ו- %f למספרים
ממשיים. שימו לב לסימן & לפני שם המשתמש ch. סימן זה הוא חובה בפרוצדורת scanf, והוא קובע כי אנו
נקלוט ערך ונציבו בתוך תא הזכרון שהמשתנה מצביע עליו. לאחר מכן, נדפיס טקסט יחד עם ערך. גם כאן,
printf מקבלת את סימן %d ומחכה לשם המשתמש כארגומנט נוסף.
כעת נוכל ללמוד על אופרטורים אריתמטיים.
האופרטורים הללו הם סימנים שמורים המשמשים אותנו לביצוע פעולות חשבוניות על המספרים (כגון חיבור,
חיסור, כפל וחילוק). נוכל להשתמש בהם ללא קושי. התוכנית הבאה מקבלת שני ערכים מספריים, ומתפקדת
כמחשבון. שימו לב לתוכנית הבאה:
#include <stdio.h>
void main(void)
{
int a=0,b=0;
printf("Please enter two numbers:\n");
scanf("%d %d",&a, &b);
printf("\n%d + %d = %d",a,b,a+b);
printf("\n%d - %d = %d",a,b,a-b);
printf("\n%d * %d = %d",a,b,a*b);
printf("\n%d / %d = %d",a,b,a/b);
}
כפי שמוצג, האופרטור + מסמן חיבור, - מסמן חיסור, * מסמן כפל ו- / מסמן חילוק. כעת מוצגת לנו בעייה.
מה ייקרה אם מספר b יהיה שווה לאפס? כמובן שהחיבור, החיסור והכפל יעבדו, אך כיצד פעולת החילוק תגיב?
למעשה, התוכנית תציג לנו הודעת שגיאה, מכיוון שהחילוק באפס אסור מבחינה מתמטית. לכן, נוכל להמנע מכך
על ידי שימוש בתנאים. תנאים (Conditions) הם התנייה של שיוויונים, אי שיוויונים, פעולות בוליאניות
כמו פעולת גדול מערך או קטן מערך. נשתמש בתנאי לצורך המנעות מהודעת שגיאה:
#include <stdio.h>
void main(void)
{
int a=0,b=0;
printf("Please enter two numbers:\n");
scanf("%d %d",&a, &b);
printf("\n%d + %d = %d",a,b,a+b);
printf("\n%d - %d = %d",a,b,a-b);
printf("\n%d * %d = %d",a,b,a*b);
if(a!=b)
printf("\n%d / %d = %d",a,b,a/b);
}
כמו שאנו רואים, סימן קריאה ואחריו שווה מסמן לנו אי שיוויון בשפת C. נוכל להחזיר הודעת שגיאה משלנו
באמצעות שימוש במילה השמורה else. מילה זו פותחת לנו בלוק נתונים במידה והתנאי לא התקיים. התוכנית
שלנו לאחר שינוי קל, תראה כך:
#include <stdio.h>
void main(void)
{
int a=0,b=0;
printf("Please enter two numbers:\n");
scanf("%d %d",&a, &b);
printf("\n%d + %d = %d",a,b,a+b);
printf("\n%d - %d = %d",a,b,a-b);
printf("\n%d * %d = %d",a,b,a*b);
if(a!=b)
printf("\n%d / %d = %d",a,b,a/b);
else
printf("\nError! Division in zero!");
}
כעת נרחיב קצת לגבי התנאים. התנאים עובדים לפי אופרטורים לוגיים, כמו בדיקה לשיוויון, גדול ממשהו,
קטן ממשהו, ובמקרה שלנו, אי שיוויון. הסימן לשיוויון הוא ==, הסימן לאי שיוויון הוא !=, הסימן לגדול
ממשהו הוא <, הסימן לקטן ממשהו הוא >, הסימן לגדול למשהו וגם שווה הוא <=, והסימן לקטן ממשהו
וגם שווה הוא >=. בפעולת השוואה אנו יכולים להשוות כל גודל, אפילו כאלה שלא מתאימים לסוג המשתנה.
לדוגמא, השוואת ערך מספרי עם char ישווה את קוד האסקיי (Ascii Code) של התו עם המספר. נרחיב על כך
מאוחר יותר. כאשר אנו עוסקים בהשוואה, אנחנו יכולים לתת כמה תנאים ביחד, ולשלבם לתוך משפט תנאי אחד
ויחיד. נעשה זאת על ידי שימוש בשני אופרטורים: אופרטור כפל לוגי (&&), ואופרטור חיבור לוגי (||). אם
נרצה לבדוק את קיומם של שני תנאים, נשתמש בכפל הלוגי. הכפל הלוגי נקרא גם שער And, ואם נדמה אותו
למעגל חשמלי, הוא יהיה מעגל טורי עם שני מפסקים, כך ששני המפסקים צריכים להיות סגורים על מנת שיוכל
לעבור זרם חשמלי במעגל. להלן תוכנית המשתמשת בכפל הלוגי בתוך תנאי:
#include <stdio.h>
void main(void)
{
int a=0;
printf("Please enter a number:\n");
scanf("%d",&a);
if(a>=0 && a<=9)
printf("\nThis is a digit.");
else
printf("\nThis is not a digit.");
}
תוכנית זו פשוטה למדי: היא מקבלת ערך מספרי, ובודקת האם הוא ספרה או לא. כאן היה לנו שימוש בשני
תנאים: המשתנה צריך להיות גדול או שווה לאפס, וגם (And) קטן או שווה לתשע. יש צורך להדגיש שניתן
לכתוב לתוך משפט תנאי אחד יותר משני תנאים, אלא גם שלושה, ארבעה ואפילו חמישה. כעת נדבר על אופרטור
החיבור הלוגי (||). אופרטור החיבור הלוגי ידוע גם כשער Or, ואם נדמה אותו למעגל חשמלי, הוא יהיה
מעגל עם שני מפסקים בחיבור מקביל: מספיק שרק אחד מהם יהיה מחובר על מנת שיעבור זרם. נבדוק את השימוש
בחיבור הלוגי על ידי התוכנית הקודמת. גם תוכנית זו תקבל מספר ותבדוק אם המספר הוא ספרה, אך הפעם,
הבדיקה תהיה על דרך השלילה. להלן התוכנית:
#include <stdio.h>
void main(void)
{
int a=0;
printf("Please enter a number:\n");
scanf("%d",&a);
if(a<0 || a>9)
printf("\nThis is not a digit.");
else
printf("\nThis is a digit.");
}
במקרה זה, התנאי שלנו היה בדיקה אם המספר הוא לא ספרה. הצבנו תנאי: אם המספר קטן מאפס, או (Or) הוא
גדול מתשע, אז זוהי אינה ספרה. אחרת (Else), זוהי ספרה. כעת נדגיש, שלשער And (כפל לוגי) קדימות,
כלומר, אם יהיה לנו משפט תנאי עם סוגי שערים שונים, שער And יתקיים לפני שער Or במקרה ואין סוגריים.
דוגמא טובה לכך היא התוכנית הבאה:
#include <stdio.h>
void main(void)
{
int a=0;
printf("Please enter a number:\n");
scanf("%d",&a);
if(a>=0 && a<=5 || a>5 && a<=9)
printf("\nThis is a digit.");
else
printf("\nThis is not a digit.");
}
שימו לב כי קודם הוא יבדוק את התנאים של שער And, ורק לאחר מכן יצליב בעזרת שער Or. כעת, מה נעשה אם
ברצוננו לבצע כמה דברים עבור קיום תנאי? פשוט מאוד: נפתח בלוק פקודות חדש עבורו. להלן תוכנית לדוגמא
שמשתמשת בבלוק חדש לאחר קיום תנאי:
#include <stdio.h>
void main(void)
{
char a=0;
printf("Please enter a character:\n");
scanf("%c",&a);
if(a>='A' && a<='Z' || a>='a' && a<='z')
{
printf("\n%c is an English character!!!", a);
printf("\nThis was a sample of a block...");
printf("\nBlock is done... Nothing to see here...");
}
else
printf("\n%c is not an English character... Sorry!", a);
}
כמובן שיכולנו לעשות פקודה אחת אם התנאי מתקיים (ואז לא היה צורך לפתוח בלוק בכלל) או שיכולנו לעשות
כמה פקודות במקרה שהתנאי לא מתקיים (Else), ואז היה צורך לפתוח בלוק אחרי else. כפי שאנו רואים, שפת
C היא גמישה ביותר. הכל תלוי במבנה שאנו קובעים לשפה.
לולאות
בתחילת המאמר דיברנו על כתיבת אותו פלט כמה פעמים, באמצעות שימוש בקבועים (Constants). הרעיון יפה
לכתיבה של שתי שורות, שלוש או אפילו ארבע, אבל מה נעשה אם ברצוננו לכתוב את אותה שורה מאה פעמים? או
אלף? זה יהיה מאוד לא יעיל לכתוב את אותו הקוד אלף פעמים... לשם כך נועדו הלולאות. הלולאה הראשונה
שנעסוק בה היא לולאת For. לולאה זו משתמשת במשתנה עזר, ועוברת ממספר מסויים למספר מסויים באמצעות
קיום תנאי. התחביר שלה לא מסובך. להלן שימוש בלולאה זו:
#include <stdio.h>
void main(void)
{
int i;
for(i=1;i<=7;i++)
printf("Hello!!! This is line #%d\n",i);
}
כפי שאנו רואים, התחביר של הלולאה הוא שימוש במילה השמורה for, לאחר מכן פתיחת סוגריים רגילות. בתוך
הסוגריים הללו ניתן למשתנה העזר שלנו (במקרה זה, i) ערך התחלתי (במקרה זה, 1). לאחר מכן ניתן תנאי
הקשור למשתנה i, ולאחר מכן נציין את הגידול במשתנה i או את ההקטנה של משתנה i. יש צורך להדגיש כי
לתנאי שאנו מקצים חשיבות מכרעת: כל עוד התנאי מתקיים, הלולאה תמשיך. אם ניתן תנאי שגוי לוגית, יש
סיכוי כי נקלע ללולאה אינסופית (כלומר, לולאה שאינה נגמרת לעולם). הבהרה נוספת: במקרה זה הגדרנו את
הגידול במשתנה i באמצעות שימוש באופרטור ++, שפירושו לאחר שם המשתנה יהיה העלאה באחד. כמו כן, יש
אופרטור להחסרה באחד, שהוא האופרטור -- לאחר שם המשתנה. באותה מידה, יכולנו לכתוב כך:
#include <stdio.h>
void main(void)
{
int i;
for(i=1;i<=7;i=i+1)
printf("Hello!!! This is line #%d\n",i);
}
שימו לב להגדרה של i עכשיו. חשוב גם לשים לב לגבי ההעלאה או ההקטנה של ערכו של משתנה העזר. אם נכתוב
ביטוי שלא יתאים לוגית, נקלע ללולאה אינסופית. כמובן שיכולנו גם לפתוח בלוק לאחר הגדרת הלולאה, ועל
ידי כך להכניס מספר פקודות לתוך קוד הלולאה. נוכל גם לבנות לולאה בתוך תנאי, תנאי בתוך לולאה ואפילו
לולאה בתוך לולאה. הכל תלוי במטרת התוכנית שאנו כותבים. הסוג השני של לולאות שאנו נכתוב הוא לולאת
תנאי מסוג While. לולאה מסוג זה תחזור על עצמה כל עוד תנאי מסויים מתקיים. לדוגמא:
#include <stdio.h>
void main(void)
{
int num=0;
printf("Please enter a number:\n");
scanf("%d",&num);
while(num<10)
{
printf("\nYour number is less than 10!!!");
printf("Please enter a number:\n");
scanf("%d",&num);
}
}
מה שתוכנית זו עושה, הוא לקבל מספר מהמשתמש. אם המספר יהיה קטן מעשר, הוא ימשיך לקלוט את המספר בתור
קלט סטנדרטי מהמשתמש, עד שהמספר יהיה גדול מעשר. אם המספר יהיה גדול או שווה לעשר הלולאה פשוט לא
תתקיים. כפי שאנו רואים, הבדיקה של תנאי הלולאה נעשה בתחילת הלולאה. למעשה, נוכל לכתוב את הלולאה כך
שהתנאי שלה ייבדק בסופה ולא בהתחלתה. נשתמש בלולאת while, אך הפעם גם במילה השמורה do. להלן תוכנית
הממשיכה לקבל מספר עד שהיא תקבל מספר זוגי מהמשתמש:
#include <stdio.h>
void main(void)
{
int num=2;
do
{
printf("\nPlease enter a number:\n");
scanf("%d",&num);
} while(num%2==0);
}
שימו לב שלמרות שנתתי ערך התחלתי 2 למשתנה שלנו, הלולאה תתקיים בפעם הראשונה, מכיוון שהבדיקה היא
בסופה של הלולאה ולא בהתחלתה. כמו כן שימו לב לסימן האחוז (%). אופרטור זה נקרא מודולו, והוא למעשה
מחזיר שארית חלוקה. לדוגמא, השארית של ארבע מעשר היא שתיים, השארית של שלוש מתשע היא אפס והשארית של
תשעים ממאה היא עשר. ניתן להסיק כי אם השארית של שתיים ממספר מסויים היא אפס, אז אותו מספר מסויים
הוא למעשה זוגי.
עוברים לתיכנות מתקדם
עד עתה עסקנו בבסיס של שפת C. כרגע נתחיל להתקדם קצת, לעלות שלבים וליצור תוכניות חזקות יותר. נתחיל
עם שימוש בספריות חיצוניות. עד עתה השתמשנו בספרייה stdio.h, שהיא הסטנדרטית של שפת C, אך למעשה יש
עוד ספריות רבות. הספרייה שנתעסק איתה עכשיו נקראת conio.h. ספרייה זו אחראית על גרפיקה בסיסית, על
קלט מקשים ועל פוקנציות חשובות אחרות. להלן תוכנית שמשתמשת בספרייה זו:
#include <stdio.h>
#include <conio.h>
void main(void)
{
char ch=0;
printf("Enter a character:\n");
ch=getch();
clrscr();
printf("\nYour character: %c\nAscii code: %d", ch, ch);
}
כעת עבדנו על כמה פונקציות חדשות. בהתחלת התוכנית הכרזנו על משתנה תו ששמו הוא ch. הדפסנו פלט למסך
המחשב, ולאחר מכן קלטנו את התו מהמקלדת. פונקציית getch מקבלת מהמקלדת לחיצה יחידה. מי שמכיר את שפת
C יודע שיש שתי פונקציות שעושות את זה: getch היא הראשונה, getche היא השנייה. ההבדל ביניהן היא
שפונקציית getche מדפיסה באופן אוטומאטי את התו למסך אחרי קליטתו, בעוד getch לא. לאחר קליטת התו מן
המקלדת, ניקינו את הפלט על המסך על ידי שימוש בפרוצדורה clrscr. לאחר מכן הדפסנו את התו שלנו בשתי
צורות: בצורה תווית ובצורה מספרית. הצורה התווית היא ברורה לחלוטין, אך מה לגבי הצורה המספרית? כאן
נעצור רגע ונסביר מספר דברים לגבי תווים. במקלדת אמריקאית סטנדרטית יש 255 תווים, המסודרים בטבלה
שנקראת טבלת אסקיי (Ascii Table). בטבלה זו, כל תו על המסך מקבל ערך מספרי. מערכת ההפעלה מזהה את
הערך המספרי הזה, ובעזרת הטבלה מתרגמת את הערך המספרי לתו, והתוצאה היא תו על המסך. טבלת אסקיי היא
לא היחידה, ישנן טבלאות רבות, והידועה ביניהן היא יוניקוד (Unicode). שפת C נוגעת בטבלת האסקיי (בכל
הנוגע לתיכנות מתחיל). כאשר אנחנו מדפיסים את הערך המספרי של תו, אנו מדפיסים למעשה את קוד הזיהוי
שלו בטבלת האסקיי. השימוש הבא שלנו בפונקציות של ספריית conio.h יהיה שימוש בצבעים. הצבע של הטקסט
שלנו הוא תמיד אפור על רקע שחור, אך נוכל לשנות זאת. להלן תוכנית המדפיסה את על הצבעים:
#include <stdio.h>
#include <conio.h>
void main(void)
{
int i;
clrscr();
for(i=0;i<=15;i++)
{
textcolor(i);
cprintf("%d\n",i);
}
}
כפי שאנחנו רואים, יש לנו שישה עשר צבעים סטנדרטיים. אם תריצו את התוכנית, לא תראו את אפס, מכיוון
שאפס הוא שחור. כמו כן, נוכל לשנות את רקע צבע הפלט באותה דרך באמצעות textbackground. כמו כן, נוכל
תמיד לכתוב את שם הצבע באותיות אנגליות גדולות במקום לכתוב את המספר. למשל, במקום לכתוב 9 בתוך
פרוצדורת textcolor, נוכל פשוט לכתוב LIGHTBLUE. אני ממליץ להציץ בקוד המקור של conio.h, וכמו כן
בתוך קבצי העזרה של סביבת העבודה שלכם, הם יכולים לחשוף הרבה יותר ממה שהמאמר הזה יוכל לגלות לכם.
שימו לב רק שכתבנו cprintf במקום printf. התוספת של c פירושה Color, כלומר, הפלט שלנו יוגדר לצבעים.
כעת נעסוק בחלונות גרפיים. חלון גרפי משתמש בצבע הרקע הנוכחי של הטקסט. הוא מוגדר באמצעות הנקודה
השמאלית העליונה, והנקודה הימנית התחתונה, כלומר, שני ערכי x ושני ערכי y על המסך. כדי לנקות את כל
החלונות הגרפיים נשתמש בפרוצדורת clreol. להלן תוכנית היוצרת חלון גרפי פשוט:
#include <stdio.h>
#include <conio.h>
void main(void)
{
textcolor(15);
textbackground(9);
window(5,5,75,20);
clrscr();
cprintf("This is a test...");
getch();
clreol();
clrscr();
cprintf("This is also a test...");
}
שימו לב כי עשינו פעולת ניקוי מסך כדי שנראה את החלון שהגדרנו. כמו כן, שימו לב לפונקציית getch. אף
משתנה לא מקבל את הערך שהיא מחזירה, כלומר, פונקצייה זו לא מחזירה ערך, אלא מתפקדת הפעם כפרוצדורה.
התוכנית מקבלת את הערך שהפונקצייה מחזירה ומכניסה אותו לתוך Null, ושם המידע נעלם. Null הוא המקום
בו הערכים "נבלעים". כעת נלמד על אקראיות בשפת C. נוכל להגריל מספר אקראי (ראנדומלי) בעזרת פונקצייה
שנמצאת בתוך ספריית stdlib.h. הפונקצייה למעשה מחזירה מספרים אקראיים בין אפס לגבול העליון שנקבע לה
לא כולל את הגבול העליון. להלן תוכנית שמגרילה מספר אקראי בין אפס למאה (כולל מאה):
#include <stdio.h>
#include <stdlib.h>
void main(void)
{
int a;
randomize();
a=random(101);
printf("%d",a);
}
שימו לב לפרוצדורת randomize. פרוצדורה זו מפעילה גנרטור אקראיות (Random Generator). אם לא היינו
קוראים לפרוצדורה זו, התוכנית הייתה אקראית לכאורה, כלומר, מחזירה מספרים אקראיים בכל שלב בתוכנית,
אך כל קריאה מחדש לתוכנית הייתה מניבה את אותם מספריים אקראיים. לכן, רצוי מאוד לקרוא לפרוצדורה זו
לפני הגרלת מספר אקראי. מה אם נרצה להגריל מספר בין מאה למאתיים? נשתמש בחיבור כמובן! להלן תוכנית
המגרילה מספר בין מאה למאתיים:
#include <stdio.h>
#include <stdlib.h>
void main(void)
{
int a;
randomize();
a=random(101)+100;
printf("%d",a);
}
מה שהתוכנית עושה הוא להגריל מספר בין אפס למאה, ואז להוסיף לו מאה, כלומר, המספר יהיה בין מאה
למאתיים. תרגלו את הפונקציות שעליהן דיברנו כעת, וכמו כן חקרו קצת את הספריות הסטנדרטיות.
מערכים
מה ייקרה אם נרצה לבנות תוכנית עם מאה משתנים? או אלף? הרי לא נגדיר אלף משתנים. מה שנעשה הוא לבנות
מערך. מערך (Array) הוא הגדרת שם לקבוצה גדולה של משתנים בעלי מכנה משותף -
הטיפוס שלהם או במילים אחרות סוג המשתנה שלהם. פנייה למשתנים תהיה באמצעות שם המשתנה,
ומקומו המספרי במערך. יש דיון גדול לגבי התחלת המערכים (האם מערך מתחיל באחד או באפס). במאמר זה אנו
נשתמש במערכים המתחילים מאחד. ההגדרה למערך היא כמו למשתנה רגיל, רק אחרי שם המשתנה כותבים בסוגריים
מרובעות את מספר איברי המערך. הנה דוגמא לתוכנית המכילה מערך:
#include <stdio.h>
void main(void)
{
int a[7];
for(i=1;i<=7;i++)
scanf("%d",&a[i]);
}
כפי שאנו רואים, הפנייה לאיבר במערך דומה מאוד לפנייה למערך רגיל. מערך מסוג זה נקרא מערך חד מימדי.
ניתן לבנות גם מערכים דו מימדיים. התוכנית הבאה משתמשת במערך דו מימדי:
#include <stdio.h>
void main(void)
{
int a[3][3],i,k,b=1;
for(i=1;i<=3;i++)
{
for(k=1;k<=3;k++)
{
printf("\nPlease enter row #%d, col #%d: ",i,k);
scanf("%d",&a[i][k]);
}
}
for(i=1;i<=3;i++)
printf("\nSum of #%d row: %d.",i,a[i]+a[i+1]+a[i+2]);
}
תוכנית זו מקבלת מהמשתמש ערכים למערך דו מימדי בן שלוש שורות ושלוש עמודות, ולאחר מכן מדפיס את סכום
הערכים של כל שורה. יש להזהר מחריגה מגבולות המערך: אם יש לנו מערך בן ארבעה איברים, וננסה לפנות
לאיבר החמישי, נקבל הודעת שגיאה.
מחרוזות
כמובן שאנחנו יכולים לעשות מערכים מסוגים שונים. כעת נדבר על מערך של תווים. מערך של תווים נקרא
מחרוזת (String). מחרוזת היא למעשה טקסט שאורכו נע בין 0 ל- 255 תווים, כלומר, זהו מערך דינאמי.
גם בתוכנית מחרוזת מוגדרת כמערך של תווים. יש שתי דרכים עיקריות לקליטת מחרוזת. הדרך הראשונה היא
הברורה לנו: שימוש בפקודת scanf. הנה דוגמא לקלט מחרוזת:
#include <stdio.h>
void main(void)
{
char a[255]="";
printf("Please write your name: ");
scanf("%s",&a);
printf("\nYour name is: &s",a);
}
זוהי הדרך "המסורתית" לקלט של מחרוזת, אך למעשה במהדרים רבים קיימות שגיאות הנוגעות לקליטת מחרוזת
בדרך זו. לכן בנו לנו המתכנתים היקרים את ספריית string.h. ספרייה זו מכילה פונקציות ופרוצדורות
רבות וטובות הנוגעות לשימוש במחרוזות. כעת נוכל להשתמש בקלט ופלט אחרים למחרוזת. הנה תוכנית מקבילה
לתוכנית הקודמת: היא מבצעת את אותן פעולות, אך התוכנית שלהלן תשתמש בפונקציות מתוך ספריית המחרוזות.
להלן התוכנית:
#include <stdio.h>
#include <string.h>
void main(void)
{
char a[255]="";
printf("Please write your name: ");
gets(a);
printf("\nYour name is: ");
puts(a);
}
כמו שאנו רואים, יש לנו את פרוצדורת gets שקולטת מחרוזת (שימו לב שאין סימן &), ופרוצדורת puts: פלט
של המחרוזת בשלמותה. אל לנו לשכוח כי מחרוזת היא מערך של תווים, ולנו יש את האפשרות לפנות לכל איבר
במחרוזת על פי מיקומו. על מנת שנוכל לגלות את האורך הדינאמי של המחרוזת, נשתמש בפונקציית strlen, גם
היא לקוחה מתוך string.h. להלן תוכנית שהופכת את כל האותיות האנגליות הגדולות בתוך מחרוזת לאותיות
אנגליות קטנות:
#include <stdio.h>
#include <string.h>
void main(void)
{
int i;
char a[255]="";
printf("Please write a string: ");
gets(a);
for(i=1;i<=strlen(a);i++)
if(a[i]>='A' && a[i]<='Z')
a[i]=a[i]+32;
printf("\nLowercased new string: ");
puts(a);
}
ההבדל בין אותיות גדולות לקטנות בטבלת אסקיי הוא 32, ולכן אם התוכנית מזהה את האות במחרוזת כאות
גדולה, התוכנית הופכת אותה לאות קטנה. ישנן פונקציות ופרוצדורות נוספות בתוך string.h. פרוצדורה
חשובה ביותר היא strcpy, שמעתיקה מחרוזת אחת לשנייה (אי אפשר לכתוב פעולת השמה רגילה במערכים). עוד
פרוצדורה היא strcat, שמוסיפה ערך נוסף למחרוזת נתונה. פונקציות נוספות שכדאי להציץ בתעוד שלהן בתוך
קבצי העזרה הן: strcmp, strncmp, שאחראיות על השוואה בין שתי מחרוזות. יש עוד הרבה מה להרחיב בנושא
המחרוזות, וזאת ייעשה במאמר אחר.
כתיבת פונקציות ופרוצדורות
כמו כל מתכנת, אי אפשר בלי לכתוב פונקציות ופרוצדורות משלך. כתיבת פונקצייה ופרוצדורה היא למעשה
אותה פעולה. למעשה, עד עכשיו כתבנו כל הזמן פונקצייה שנקראת main. התחביר של כתיבת פונקצייה צריך
להיות ברור. אם התחביר לא ברור, הנה פונקצייה לדוגמא, שמקבלת שני מספרים ומחזירה את סכומם:
#include <stdio.h>
int addnums(int a, int b)
{
int c;
c=a+b;
return c;
}
כמו שאנו רואים, הפונקצייה תתחיל בסוג הערך שהיא מחזירה. במקרה שלנו, מספר שלם. לאחר מכן, אנו ניתן
שם לפונקצייה (אסור לעשות שימוש במילה שמורה). לאחר מכן פתיחת סוגריים, וביניהן הערכים שהפונקצייה
יכולה לקבל. שימו לב כי אנו כותבים פעמיים int, עבור כל ערך כותבים סוג בכתיבת פונקצייה. לאחר מכן
יש לנו את קוד הפונקצייה. שימו לב כי המשתנה c הוא משתנה לוקאלי: הוא מוגדר רק בתוך הפונקצייה. מחוץ
לפונקצייה הוא לא מוגדר. זוהי גם סיבה טובה לשימוש בפונקציות ובפרוצדורות: יש שחרור של זכרון ברגע
שהפונקצייה או הפרוצדורה סיימו את תפקידיהן, ולכן שימוש בהן פירושו חסכון בזכרון. שימו לב גם למילה
השמורה return, שמחזירה את הערך שמופיע אחריה. למען האמת, משתנה c מיותר כאן לחלוטין, והיינו יכולים
לכתוב פונקצייה כזו:
#include <stdio.h>
int addnums(int a, int b)
{
return a+b;
}
הקריאה לפונקצייה או לפרוצדורה מתוך פונקצייה או פרוצדורה אחרת מותרת רק אם המהדר קרא את הפרוצדורה
או את הפונקצייה לפני כן. לדוגמא, נסיון הידור של תוכנית זו תגרום לשגיאה:
#include <stdio.h>
void main(void)
{
printf("%d",addnums(5,7));
}
int addnums(int a, int b)
{
return a+b;
}
זה יגרור הודעת שגיאה, מכיוון שפונקציית main עדיין לא מכירה את פונקציית addnums. כאן יש לנו שתי
אפשרויות לפתרון. האפשרות הראשונה היא ההגיונית ביותר: לכתוב את main אחרי addnums. נעשה זאת בצורה
הבאה:
#include <stdio.h>
int addnums(int a, int b)
{
return a+b;
}
void main(void)
{
printf("%d",addnums(5,7));
}
האפשרות השנייה לפתרון בעייה זו היא הכרזה על הפונקצייה לפני main, והגדרתה לאחר main, כך:
#include <stdio.h>
int addnums(int a, int b);
void main(void)
{
printf("%d",addnums(5,7));
}
int addnums(int a, int b)
{
return a+b;
}
הערה חשובה: המשתנים המוגדרים בפונקציית main הם למעשה גם לוקאליים: הם מוגדרים רק בתוך main. נוכל
להגדיר את המשתנים לכל התוכנית על ידי כתיבתם ממש אחרי הנחיות include, בדיוק באותה הדרך שבה הגדרנו
כעת את הפונקצייה addnums.
סיום
אני יודע שלא כיסיתי אפילו רבע של שפת C, אבל אני מקווה שתרמתי לאנשים שמנסים להתחיל לתכנת. הציבו
לעצמכם אתגרים: בנו תוכניות מעניינות, כגון משחקי מחשב, עזרים לחישובים מתמטיים, תוכניות גרפיות
ועוד. זה דבר שתלמדו אך ורק מנסיון, ולא מבית הספר, מחוג מחשבים מתקדם, מספר לימוד או ממאמר שהופיע
באתר התיכנות הישראלי. שלכם, קרוסבוו.