עבודה עם קבצים
קבצים הם משאב מערכת בדומה לזיכרון ולכן:
- צריך לפתוח קובץ לפני השימוש בו
- צריך לסגור קובץ בסוף השימוש בו
- אותן בעיות אשר צצות מניהול זכרון לא נכון עלולות לצוץ מניהול קבצים לא נכון
בדומה לעבודה עם הקלט והפלט הסטנדרטיים, קריאה וכתיבה מקבצים מתבצעת באמצעות -ים המוגדרים בספריה .
- המחלקה משמשת לכתיבה לקובץ
- המחלקה משמשת לקריאה מקובץ
לשתי המחלקות הנ"ל יש בנאי המקבל את שם הקובץ לפתיחה והורס המוודא שהקובץ נסגר כאשר העצם נהרס.
כתיבה וקריאה מתבצעות ע"י שימוש ב- ו- בדומה לקלט והפלט הסטנדרטיים ושאר הפעולות על ערוצי הפלט והקלט מתבצעות כשיטות(מטודות) של המחלקות. (למשל השיטה מחזירה האם הגענו לסוף הקובץ)
ניתן גם לקרוא מקובץ באמצעות הפקודה . (שהיא לא שיטה, אלא פונקציה חיצונית)
לא ניתן להעתיק אובייקטים מסוג . כדי להעביק קבצים לפונקציה אנו נשתמש במצביע/רפרנס.
#include <fstream>
using namespace std;
int main()
{
ofstream name("output.txt");
name << "Hello, World!" << endl;
return 0;
}
כתבו את התוכנית המקבלת שני קבצים ומעתיקה אחד לשני.
#include <fstream>
int main(int argc, char** argv)
{
if (argc < 3)
{
cerr << "Not enough arguments..." << endl;
return 1;
}
ifstream inFile(argv[1]);
ofstream outFile(argv[2]);
string s;
while (!in_file.eof())
{
getline(in_file, s);
out_file << s << endl;
}
return 0;
}
אם קובץ הפלט קיים אז הוא ידרס.
אם קובץ קלט לא קיים אז הוא יווצר.
חלוקה למודולים
אורך החיים של הקוד יכול להיות עשרות שנים ולאורך תקופה זו יש לתחזק את הקוד:
- לתקן באגים
- להכניס תכונות חדשות
- להתאים להתקנים חדשים
הכנסת שינויים לתוכנה קיימת עלולה ליצור באגים חדשים וככל שהתוכנה מסובכת יותר, כך הסיכוי לבאגים גדול יותר.
בתכנות מודלרי אנו מחלקים את הקוד למודולים לפי תפקידם. זה מאפשר:
- שימוש חוזר במודולים עבור תוכנות אחרות
- הסתרת פרטי מימוש
- הקטנת התלות בקוד עצמו
המודול
מודול תוכנה מחולק לשני חלקים: הממשק והמימוש.
הממשק ():
- מגדיר את הפעולות שניתן לעשות בעזרת המודול
- חלק זה חשוף למשתמש במודול
המימוש ():
- מספק את המימוש לפעולות שניתן לבצע דרך ממשק המודול
- חלק זה (בדרך כלל) אינו חשוף למשתמש
ב- הדרך בה אנו בונים מודול הוא באמצעות שני סוגי קבצים: קבצי וקבצי .
קובץ יכל את הממשק:
- הכרזות על פונקציות
- הגדרות טיפוסים
- הכרזות על קבועים שיש בהם עניין למשתמש
קובץ יכיל את מימוש המודול:
- מימושי פונקציות הממשק ופונקציות פנימיות נוספות
- הגדרות טיפוסים לשימוש פנימי
- הכרזות על קבועים שאין בהם עניין למשתמש
למה שנרצה להפריד את הממשק והמימוש? כדי להסתיר מידע מהלקוח ובנוסף, לתת לנו אפשרות לשנות את המימוש בלי שהלקוח ידע על כך או שזה ישנה את הקוד של הלקוח בגלל שינויים שאנחנו עשינו.
נרצה לכתוב מודול המאפשר עבודה עם וקטורים במישור -
המודול יכיל את האלמנטים הבאים:
- הגדרת מבנה הנקרא
vector2Dעם שדות אליהם יוכל לגשת המשתמש - קבוע בשם
zero_vectorהמייצג את וקטור האפס - פונקציה בשם
lengthהמקבלת וקטור ומחזירה את האורך שלו - פונקציה פנימית בשם
dotProductהמחזירה את תוצאת המכפלה הסקלרית של שני וקטורים.
בקובץ יהיה המבנה, הקבוע וההכרזה על length.
בקובץ יהיה המימוש של length וההכרזה והמימוש של הפונקציה dot_product
הקובץ יהיה:
#pragma once
struct vector2D
{
double x;
double y;
};
const vector2D zero_vector = {0, 0};
double length(const vector2D& v);
הקובץ יהיה:
#include <cmath>
#include "vector2d.h"
using namespace std;
static double dot_product(const vector2D& v1, const vector2D& v2)
{
return (v1.x * v2.x) + (v1.y * v2.y);
}
double length(const vector2D& v)
{
return sqrt(dot_product(v, v));
}
הידור של מספר קבצים
כדי להדר מספר קבצים ניתן פשוט לרשום את כל הקבצים כפרמטרים לשורת הפקודה של :
g++ a.c b.c mytest.c
את ההידור ניתן לחלק לשלושה שלבים:
- עיבוד מקדים -
- הידור -
- קישור -
1. עיבוד מקדים
שלב העיבוד המקדים עור על קובץ הקוד ומבצע את ההוראות עבור ה- שהן כל השורות המתחילות ב-.
פעולות העיבוד המקדים הן כולן פעולות גזירה והדבקה פשוטות.
הגדרת מאקרו
#define <macro> <value>
לאחר ההגדרה, בכל מקום שיופיע <macro> בקוד, תבוצע ההחלפה ל-<value>.
ניתן להגדיר יותר מסתם קבועים בעזרת מאקרו אבל חשוב לשים לב להשלכות.
הוספת תוכן קובץ
#include <filename>
מוסיף את תוכן הקובץ לתוך הקוד במיקום הנוכחי.
- הקוד שמתווסף עובר גם הוא עיבוד מקדים
- אם שם הקבוץ מופיע בתוך ההתייחסות היא לקובץ מהספריה הסטנדרטית
- אם שם הקובץ מופיע במרכאות " " ההתייחסות היא לקובץ שנמצא עם שאר הקוד שכתבנו
2. הידור
שלב ההידור מקבל קבצי קוד מקור שעברו את העיבוד המקדים. הוא עדיין לא מקבל קבצי אלא רק קבצי (אשר אליהם הודבקו קבצי ה- בעיבוד המקדים)
לכל קובץ נוצר קובץ בינארי אשר מכיל את הקוד המהודר מקובץ הקוד. לקובץ זה קוראים object file ויומתו היא
ייתכן וחלק מהפונקציות שהוכרזו לא מומשו בקובץ הנוכחי ולכן קובץ ה- יכיל "חורים"
3. קישור
שלב הקישור מקבל כקלט קבצי אובייקט ומקשר את כולם לקובץ הרצה יחיד.
בשלב זה, לכל "חור" שהושאר בקובץ אובייקט מקושרת הפונקציה המתאימה(אם אכן קיים לה מימוש באחד מקבצי האובייקט)
ייתכנו שגיאות בשלב זה הנקראות שגיאות קישור:
- לא נמצא מימוש עבור פונקציה מסויימת:
a.c:11: undefined rerfence to 'my_function' - נמצא יותר ממימוש אחד עבור פונקציה מסויימת:
a.c:10: multiple definition of 'main' b.c:10: first defined here
בקישור של קובץ הרצה, רק אחד הקבצים צריך להכיל הגדרה של פונקציית .
פונקציות סטטיות גלובליות
כאשר מגדירים פונקציה סטטית מחוץ למחלקה, אנו אומרים בעצם שאפשר להשתמש בפונקציה אך ורק בתוך הקובץ שהיא נכתבה בו.
נשתמש בזה בשביל פונקציות עזר שאינן חלק מהממשק.
פונקציות סטטיות אינן מוכרזות בקובץ .
static int square(int n)
{
return n * n;
}
וידוא הנחות בזמן ריצה
ישנו המקרו אשר מאפשר לנו בזמן בדיקה בלבד לבדוק האם יש בעיות בקוד שלנו.
assert(<expression>);
המאקרו מוגדר בספרייה <cassert> ובזמן הריצה הביטוי מוערך ונבדק:
- אם הוא נכון, לא קורה כלום.
- אם הוא אינו נכון, התוכנית נעצרת ומודפסת הודעה המפרטת את מיקום ההנחה שכשלה.
ניתן לכבות את המקרו ע"י הידור התכנית עם הדגל -DNDEBUG. כך ניתן לשחרר גרסה סופית של הקוד שאינה מואטת ע"י הבדיקות ללא הסרתן ידנית.
שימו שקוד שבתוך ה- לא יורץ כלל אם המאקרו כבוי.
מתי משתמשים ב-
- ב- משתמשים לוידאו נכונות של הנחות הנעשות בקוד.
- אם ההנחות שגויות ייתכן וקיימים באגים
- נוח לבדוק עם את נכונות הארגומנטים, ערכי החזרה ואינווריאנטות של טיפוסי נתונים.
לא משתמשים ב- כדי לבדוק קלט מהמשתמש או עבור בדיקות שרוצים שיתבצעו גם בתוכנית הסופית.