שאלות נפוצות - FAQ שפות תכנות - תכנות - HWzone פורומים
עבור לתוכן
  • צור חשבון

שאלות נפוצות - FAQ שפות תכנות


שניצל

Recommended Posts

ברוכים הבאים ל-FAQ שפות תכנות!

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

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

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

קישור לתוכן
שתף באתרים אחרים

אני מנסה להגריל מספרים אקראיים, אבל אני מקבל את אותם מספרים שוב ושוב. למה?

הקדמה - מחולל מספרים אקראיים

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

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

למה אנחנו מקבלים את אותם מספרים?

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

for (int i = 0 ; i < 10 ; i++) {
Random rnd = new Random();
int x = rnd.nextInt(10)+1;
system.out.println(x);
}

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

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

איך פותרים את הבעיה?

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

Random rnd = new Random();
for (int i = 0 ; i < 10 ; i++) {
int x = rnd.nextInt(10)+1;
system.out.println(x);
}

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

באופן דומה, ב-++C/C צריך לדאוג לקרוא לפונקציה srand פעם אחת בלבד, בתחילת התכנית.

קישור לתוכן
שתף באתרים אחרים

איך דואגים שהחלון לא ייסגר כשהתכנית מסתיימת?

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

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

#C

Console.ReadKey();

הפונקציה מחכה עד שהמשתמש יקיש על המקלדת.

++C

std::cin.get();

(יש לעשות include ל-iostream)

C

getchar();

(יש לעשות include ל-stdio.h)

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

אמרו לי שאפשר ב-C להשתמש בפונקציה getch אולקרוא ל-("system("PAUSE, זה נכון?

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

אז למה לא כדאי?

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

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

ניסיתי מה שהצעתם ב-C וזה עדיין לא עובד! התכנית נסגרת מיד!

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

הפתרון הוא כמובן לדאוג לאפס את החוצץ לפני הקריאה ל-getchar. פתרון (שגוי!) שמקומות רבים ממליצים להשתמש בו הוא להוסיף לפני ה-getchar שורת הקוד הבאה:

fflush(stdin);

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

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

int ch;
while (((ch = getchar()) != '\n') && ch != EOF) { }

ב-++C ניתן לעשות משהו דומה, באמצעות הפונקציה ignore:

std::cin.ignore(INT_MAX, '\n');

קישור לתוכן
שתף באתרים אחרים

איך ניתן לפרק מספר לספרות שלו?

בקצרה - קוד לדוגמה

לדוגמה, זהו קוד שמחשב את סכום הספרות במספר 1234. הקוד יעבוד למעשה במספר שפות (C/C++, C#, Java)

int n = 1234;
int d;
int sum = 0;
while (n > 0) {
d = n % 10;
sum += d;
n = n / 10;
}

בסוף הלולאה, sum יכיל את סכום הספרות ב-1234.

הקדמה - חלוקה עם שארית

למי שלא זוכר את שיעורי חשבון, חלוקה עם שארית מתבצעת באופן הבא: נניח שיש לנו זוג מספרים שלמים a ו-b (לדוגמה, 20 ו-6). אם נשאל "כמה פעמים נכנס b בתוך a" נקבל מספר כלשהו q, ושארית כלשהי r. לדוגמה, אם a=20 ו-b=6 אז b נכנס 3 פעמים בתוך a (כי 6*3=18) ואנו נשארים עם שארית 2. אז במקרה זה, q=3 ו-r=2.

בניסוח אחר, ניתן לומר ש-r הוא המספר הקטן ביותר שיש להחסיר מ-a על מנת שיתחלק ב-b, ו-q הוא תוצאת החלוקה הזו. כמובן, ייתכן כי r הוא 0, במקרה שבו a מלכתחילה מתחלק ב-q.

הפעולות המתמטיות באמצעותן מחשבים את q ו-r הללו נקראות חילוק ומודולו, המסומנות לרוב ע"י האופרטורים / ו-% בהתאמה (לדוגמה, כך זה בשפות ++C/C, ג'אווה ו-#C). בשפות אחרות פעולות אלו מסומנות בדרכים אחרות (לדוגמה, בפסקל הן נקראות DIV ו-MOD, ובגרסאות החדשות של פייתון חלוקה בין מספרים שלמים מסומנת ע"י // במקום /).

אז איך הקוד עובד?

נניח שבידינו המספר n=1234. לשם הנוחות, מוציאים את הספרות מהנמוכה לגבוהה - כלומר נתחיל מספרת האחדות, 4. ספרת האחדות היא למעשה המספר העונה על השאלה "כמה יש להחסיר מ-1234 על מנת שיתחלק ב-10", או במילים אחרות, n % 10. זהו ההסבר לשורה:

 d = n % 10;

השורה הבאה מוסיפה את d לסכום:

 sum += d;

כמובן, יכלנו להחליף אותה בכל פעולה אחרת שמשתמשת ב-d (להכניסו למערך, להדפיס וכיו"ב).

לאחר מכן, נרצה "למחוק" מ-n את הספר 4 כדי להישאר עם המספר 123, ולהמשיך לקרוא את הספרות. כאשר נחלק את המספר ב-10, נקבל 123.4 - אבל כיוון שמדובר בחלוקה של מספרים שלמים, המחשב מעגל את התוצאה למטה ונישאר עם 123. זה מסביר את השורה הזו:

 n = n / 10;

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

קישור לתוכן
שתף באתרים אחרים

למה כשאני מחלק בין שני מספרים אני מקבל תוצאה לא נכונה?

דוגמה

נניח שכתבנו את הקוד הבא, שמחשב את התוצאה של הביטוי החשבוני 5/2:

int x = 5;
int y = 2;
double z = x/y;
printf("%lf", z);

התוצאה שהיינו מצפים לקבל היא 2.5. עם זאת, התוצאה שמודפסת על המסך היא 2.

הסבר

ברוב שפות התכנות, חלוקה בין שני טיפוסים המייצגים מספרים שלמים (כגון int, short ו-long) תמיד תניב תוצאה שהיא גם מספר שלם. טיפוסים אלו לא מסוגלים לייצג שברים, ולכן תוצאת החלוקה תמיד תעוגל כלפי מטה, כך התוצאה של הביטוי 5/2 אינה 2.5, אלא 2. למרות ששמרנו את התוצאה במשתנה מטיפוס double, השמירה הזו מתבצעת רק אחרי שפעולת החלוקה והעיגול כלפי מטה התבצעו.

פתרון

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

double z = (double)x / y;

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

double z = (double)x / (double)y;

קישור לתוכן
שתף באתרים אחרים

שאלה על ההסבר על מספרים אקראים:

למה בדוגמא הבאה יוצאים שני מספרים שונים?

static void Main(string[] args)
{
Random rnd = new Random();
Console.WriteLine(rnd.Next(10));
Console.WriteLine(rnd.Next(10));

}

האם יכול להיות שהגרעין הספיק להטען?

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

קישור לתוכן
שתף באתרים אחרים

הגרעין נטען בבנאי של Random (דהיינו בשורה rnd = new Random). הקריאה הראשונה ל-rnd.Next מוציאה את המספר הראשון מהסדרה, והקריאה השנייה ל-rnd.Next מוציאה את המספר השני מהסדרה.

בדוגמה שנתתי אנחנו קוראים ל-new Random בכל איטרציה של הלולאה.

קישור לתוכן
שתף באתרים אחרים

מה הם "ארגומנטים של שורת הפקודה" (Command Line Arguments)? איך משתמשים בהם?

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

התוכנה שאנו כותבים מסוגלת לקבל ארגומנטים כמערך של מחרוזות. נביט רגע בפונקציית ה-Main של Java ו-#C:

static void main(String[] args)

הפרמטר args הוא מערך של מחרוזות המכילות את הארגומנטים שהעברנו לתכנית. כך לדוגמה ניתן להשתמש בו:

static void main(String[] args) {
if (args.Length < 2) {
// handle error - not enough arguments
} else {
String source = args[0];
String destination = args[1];
// rest of the main function
}
}

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

ב-C (וגם ב-++C) התכנית תראה קצת אחרת:

int main(int argc, char *argv[]) {
if (argc < 3) {
// error - not enough arguments
} else {
char* source = argv[1];
char* destination = argv[2];
// rest of the main function
}
}

ההבדלים והסיבות להם הן כדלקמן:

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

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

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

איך קובעים אילו ארגומנטים יועברו לתכנית?

יש להפעיל את התכנית על ידי הפעלת קובץ ההרצה שלה, ולאחריו הארגומנטים שברצוננו להעביר, מופרדים ברווחים. לדוגמה, אם ברצוננו להפעיל את התכנית program.exe עם הארגומנטים abc, 123 ו-def, אז יש להפעיל את התכנית כך:

program.exe abc 123 def

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

ב-Visual Studio יש ללחוץ כפתור ימני על הפרוייקט, לחיצה על properties, ו(בפרוייקט #C) תחת לשונית Debug יש לכתוב את הארגומנטים בתיבת הטקסט Command Line Arguments, או (בפרוייקט ++C/C) תחת לשונית Debugging, בתיבת הטקסט Command Arguments.

ב-Eclipse יש ללחוץ על תפריט Run, שם לבחור באפשרות Run Configurations. בחלון שנפתח, יש לבחור את התכנית המתאימה מצד שמאל, בצד ימין יש לבחור את הלשונית Arguments, ולרשום את הארגומנטים בתיבת הטקסט המתאימה.

איך מעבירים ארגומנט שיש בו רווחים?

על מנת להעביר לתכנית את הארגומנט האחד abc def (בניגוד לשני הארגומנטים השונים abc ו-def), אז יש לעטוף אותו בגרשיים, דהיינו:

program.exe "abc def"

קישור לתוכן
שתף באתרים אחרים

  • 4 חודשים מאוחר יותר...

מה ההבדל בין C++, C, ו-#C?

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

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

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

מה ההבדל בין Visual C++, Visual Basic ו-Visual Studio?

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

במקביל לכך, מיקרוסופט פיתחה שפת תכנות בשם Visual Basic (ובקיצור VB). שפה זו, המבוססת על משפחת שפות ותיקה בשם Basic, הוסיפה יכולות חזקות לפיתוח יישומים עם ממשק משתמש גרפי בסביבת בצורה נוחה (ומכאן שמה - זוהי שפה "ויזואלית"). בנוסף להיותה שפת תכנות, Visual Basic הוא גם שמה של סביבת הפיתוח (IDE) שבה כותבים תוכנות בשפת Visual Basic. כלומר, בניגוד ל-++C שניתן לכתוב בה בהרבה סביבות פיתוח שונות, לשפת VB נבנתה סביבת עבודה ייחודית עם אותו שם, ולא פותחו כלים אחרים לעבודה בשפה זו.

הפיתוח של Visual Basic (השפה וסביבת העבודה) ו-++Visual C (סביבת העבודה) המשיך במקביל, עד שב-1997 איגדה את סביבות הפיתוח הללו, יחד עם כלי פיתוח אחרים, והוציאה את Visual Studio.

הגרסה האחרונה של Visual Basic, גרסה מספר 6, יצאה בשנת 1998 (יחד עם סביבת הפיתוח המתאימה לה, Visual Studio 6.0). בשנת 2001, פיתחה את תשתית NET. (דוט נט), ושפה חדשה בשם #C. סביבת הפיתוח Visual Studio שודרגה בהתאם (וכעת נקראה Visual Studio .NET). שפת התכנות , Visual Basic, שכאמור הפיתוח שלה הופסק, הוחלפה על ידי שפת תכנות חדשה בשם Visual Basic .NET (ובקיצור VB.NET), אשר מזכירה את Visual Basic המקורית (ככל הנראה על מנת לאפשר מעבר קל למפתחים הרגילים לשפה הישנה), אבל שונה ממנה משמעותית, כדי לתמוך בתשתית NET. החדשה, וכדי שתוכל להתממשק בקלות עם קוד שנכתב בשפות NET. האחרות, כגון #C. לשם הבדלה בין VB ו-VB.NET, נהוג לקרוא לשפה הישנה VB6.

כיום הגרסה המסחרית של Visual Studio מכילה את כל כלי הפיתוח של (כולל תמיכה בכל השפות שמיקרוסופט תומכת בהן, ביניהן #C++, C ו-VB.NET). בנוסף, שחררה גרסה חינמית של Visual Studio הקרויה בשם Visual Studio Express. גרסה זו מצומצמת ומכילה יכולות חלקיות. עד גרסה 2010 כל רכיב בה היה ניתן להורדה בנפרד (דהיינו, על מנת לפתח בגרסת האקספרס ב-#C וב-VB.NET היה צורך להתקין בנפרד את Visual C# Express ואת Visual Basic Express), אך מגרסה 2012 הרכיבים אוגדו יחדיו, בדומה לגרסאות המלאות.

* סביבת משולבת - Integrated Development Environment (ובקיצור IDE) היא אוסף של כלים הנועדו לפתח תוכנות בקלות - כלי לעריכת טקסט, קומפיילר, כלי לדיבאגינג ועוד - המרוכזים בדרך כלל בתוך תוכנה אחת.

קישור לתוכן
שתף באתרים אחרים

ארכיון

דיון זה הועבר לארכיון ולא ניתן להוסיף בו תגובות חדשות.

אורח
דיון זה נעול לתגובות חדשות.
×
  • צור חדש...