שפת C/פלט וקלט קבצים

מתוך ויקיספר, אוסף הספרים והמדריכים החופשי

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


כדאי לדעת:

קטעי הקוד שבפרק זה משתמשים בספרייה הסטנדרטית של הקלט/פלט. נדון בספריות באופן מעמיק יותר כאן. לעת עתה, פשוט יש לזכור לרשום בראשי הקבצים המשתמשים בקטעי הקוד שבפרק זה <include <stdio.h#

תו סיום-הקובץ EOF[עריכה]

במחרוזות ותו האפס, ראינו שיש מוסכמה לפיה תו האפס מסיים את חלק המחרוזת המכיל תוכן אמיתי (גם אם המערך ארוך יותר). באופן דומה, ישנו תו מיוחד, EOF (End of File - "סוף הקובץ". זהו למעשה בסך הכל מאקרו עם הערך 1-), המסמן לפי מוסכמה את סיום התוכן האמיתי של קובץ.

המבנה FILE והעבודה איתו[עריכה]

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

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

פתיחת קובץ[עריכה]

הקריאה לfopen והערך המוחזר[עריכה]

פתיחת קובץ מתבצעת על ידי קריאה לfopen, המחזיר מצביע לFILE. אם המצביע הנו NULL, פתיחת הקובץ נכשלה. לכן תמיד יש לבדוק את הערך המוחזר מפונקציה זו (הדבר דומה להקצאת זיכרון במידה מסויימת).

כדי לפתוח את הקובץ "try.txt", לדוגמה, אפשר לבצע זאת:

FILE *f = fopen("try.txt" , "rt");
if(!f){
  /* Handle case where couldn't open file. */
}

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

מחרוזת סוג הפתיחה[עריכה]

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

מוד הפתיחה
תווים משמעות
r פתיחת קובץ קיים לקריאה. אם לא קיים קובץ בשם האמור, הפתיחה תכשל ותחזיר NULL.
w פתיחת קובץ חדש לכתיבה. אם קיים כבר קובץ בשם האמור, תוכנו ימחק.
a פתיחת קובץ קיים לכתיבת נתונים בסופו. התוכן החדש ידרוס את תו ה-EOF, וייכתב אחריו. אם הקובץ לא קיים, הוא יווצר תחילה.
r+ פתיחת קובץ קיים לקריאה וכתיבה. אם לא קיים קובץ בשם האמור, הפתיחה תכשל.
w+ פתיחת קובץ חדש לקריאה וכתיבה. אם קיים כבר קובץ בשם האמור, תוכנו ימחק.
a+ פתיחת קובץ קיים לכתיבת נתונים בסופו. תו סיום הקובץ יוזז לסוף הנתונים החדשים שנכתוב, כך שבהצגת תוכן הקובץ, יוצג כל התוכן. אם הקובץ לא קיים, הוא יווצר תחילה.
תוכן הקובץ
תווים משמעות
t קובץ טקסט. זוהי ברירת מחדל ולכן במידה ולא נכתוב שום סוג תוכן לאחר סוג הפתיחה המחשב יחשיב את הסוג כt.
b קובץ בינרי

סגירת קובץ[עריכה]

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

הפונקציה fclose[עריכה]

הפונקציה fclose סוגרת קובץ שנפתח על ידי fopen. משתמשים בה בצורה:

fclose(<f>);

כאשר f הוא מצביע לFILE שהוחזר מקריאה מוצלחת מfopen.

חשיבות סגירת קבצים[עריכה]

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


שימו לב:

ריבוי קבצים פתוחים עלול לפגוע בביצועי המערכת.


פלט[עריכה]

הפונקציה fprintf[עריכה]

הפונקציה fprintf דומה מאד לפונקציה printf (שכבר ראינו בפלט וקלט):

fprintf(f, "This line will be written to the file.\n");


יש לשים לב לשינויים הבאים:

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

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

if( fprintf(f,"Hello world") < 0)
  ... /* Handle error. */

להלן דוגמה לשימוש בfprintf:

#include <stdio.h>


int main()
{
  FILE *const f = fopen("new_file.txt", "wt");
  
  if(!f) 
  {
    printf("Error: could not open file!\n");
    
    return -1;
  }

  fprintf(f, "This line will be written to the file.\n");
  fprintf(f, "Here is another line: %d + %d = %d", 2, 2, 2 + 2);

  fclose(f);
  
  return 0;
}

השורות:

FILE *const f = fopen("new_file.txt", "wt");

if( !f ) 
{
  printf("Error: could not open file!\n");

  return -1;
}

מייצרות קובץ בשם "new_file.txt", ופותחות אותו לכתיבה כקובץ טקסט, בודקות האם הפעולה הצליחה, ומטפלות במצב אם לא.

השורות:

fprintf(f, "This line will be written to the file.\n");
fprintf(f, "Here is another line: %d + %d = %d", 2, 2, 2 + 2);

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

השורות:

  fclose(f);
  
  return 0;
}

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


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

עוד פונקציות[עריכה]

הספריה הסטנדרטית כוללת עוד פונקציות פלט לקבצים, לדוגמה fputs וfwrite.

קלט[עריכה]

הפונקציה fscanf[עריכה]

הפונקציה fscanf דומה מאד לפונקציה scanf (שכבר ראינו בפלט וקלט):

fscanf(f, "%d", &count);

יש לשים לב למעט השינויים הבאים:

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


נניח שfscanf ביקשה לקרוא ל n משתנים. אז הקריאה מוצלחת אם הפונקציה מחזירה n. להלן דוגמה לבדיקה:

int d;
char c;
FILE* fp;
/*...open file code...*/

if( fscanf(fp, "%d %c", &d, &c) != 2)
  ... /* Handle error. */


שימו לב:

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

הפונקציה fgets[עריכה]

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

char a[50];

fgets(a, 49, f);

הקריאה:

fgets(a, 49, f);

תקלוט עד 49 תווים מתוך קובץ. אם ב49 התווים הראשונים יש מעבר לשורה חדשה (על ידי Enter), ייקלטו רק התווים עד שם. הארגומנט השלישי, f, הוא מצביע לFILE שממנו יקראו התווים.


כדאי לדעת:

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

עוד פונקציות[עריכה]

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

זרמים סטנדרטיים[עריכה]

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

  • stdin הוא מצביע ל"קובץ" שהוא למעשה המקלדת.
  • stdout הוא מצביע ל"קובץ" שהוא למעשה המסך.
  • stderr הוא מצביע ל"קובץ" שהוא למעשה פלט השגיאות שבסופו של דבר מודפס על המסך.

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

#include <stdio.h>

int main()
{
  fprintf(stdout, "Hello world\n");
  return 0;
}


שימו לב:

אין לנסות לפתוח "קבצים" אלה בעזרת fopen או לנסות לסוגרם בעזרת fclose.

שינוי ומציאת המיקום בקובץ[עריכה]

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


הפונקציה fseek[עריכה]

זזים ממקום למקום, על ידי fseek, בצורה הזאת:

fseek(<f>, <offset>, <origin>);

כאשר:

  • f הוא מצביע לFILE.
  • offset הוא מספר הבתים שיש לזוז (מספר זה יכול להיות גם שלילי).
  • origin הוא מוצא התזוזה, והוא יכול להיות אחת משלוש האפשרויות הבאות:
    • SEEK_CUR - המיקום הנוכחי
    • SEEK_SET - תחילת הקובץ
    • SEEK_END - סוף הקובץ

לדוגמה:

fseek( file, 2L, SEEK_CUR );

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


שימו לב:

ההזזות השימושיות היחידות שבטוחות לביצוע במצב טקסט הן קפיצה לתחילת הקובץ או לסופו, כלומר:
fseek( file, 0L , SEEK_SET );
fseek( file, 0L , SEEK_END );

הפונקציה ftell[עריכה]

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

long pos = ftell( file );

לאחר קריאה זו, pos יכיל את מספר הבתים מתחילת הקובץ ועד המיקום הנוכחי.


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


הפרק הקודם:
איגודים
פלט וקלט קבצים
תרגילים
הפרק הבא:
הקדם מעבד