המדריך השלם
 

מבנים

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

התבנית של מבנה:

struct name_of_struct
{
    variables
};
בהגדרת המערך יש לכתוב את המילה השמורה struct ולאחר מכן את שם המבנה שבחרנו (חשוב כי שמו של המבנה יהיה משמעותי בדיוק כמו בבחירת שמות למשתנים). בתוך הסוגריים יש לרשום את המשתנים ובסוף נקודה-פסיק (;).
לדוגמא:
struct address
{
    char city[20];
    char street[20];
    int num_house;
    long code_area;
};

יצרנו מבנה של כתובת הכולל: שם עיר ורחוב- מחרוזות בעלות 20 תווים, מספר בית- משתנה מסוג שלם, ומיקוד- מספר שלם.
כעת נצהיר על משתנה מהטיפוס כתובת:
struct address add;
כמו כן כשנרצה לגשת לשדות של המשתנה add ולעדכן אותם, נוכל לעשות זאת על ידי האופרטור נקודה (.) - לדוגמא נציב את בשדה מס' הבית 5 כך:
add.num_house = 5;
כך ניתן לעדכן ולשנות את השדות.

תבנית לגשת לשדות של מבנה:
Name_of_var.name_of_field;

ניתן להצהיר על מספר משתנים מטיפוס מבנה בשורה אחת כמו שהצהרנו על משתנים פשוטים לדוגמא: struct address add1, add2, add3.
כמו כן ניתן לאתחל מבנים כפי שאתחלנו מערך:

struct type name = {value1, value2,. . .};

לדוגמא:
struct address add = {"migdal Haemek", "Harazim", 5, 23028};

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

מבנה כפרמטר של פונקציה

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

העברה לפי תוכן:

void print_detailes (struct person p1, struct person p2,struct person p3)
{
    printf ("\n\rThe detailes of the tree persons: \n\r");
    printf ("%s %s %d \n\r", p1.f_name, p1.l_name, p1.age);
    printf ("%s %s %d \n\r", p2.f_name, p2.l_name, p2.age);
    printf ("%s %s %d \n\r", p3.f_name, p3.l_name, p3.age);
}
נניח כי אנו שולחים לפונקציה הזו 3 משתנים מסוג מבנה (המבנה הוא של אדם ושדותיו: שם פרטי, שם משפחה וגיל) אזי כדי להדפיס את שדותיהם יש פשוט להשתמש באופרטור הנקודה ובכך לפנות לשדות של המשתנים.

העברה לפי כתובת:

void get_detailes (struct person* p1, struct person* p2, struct person* p3)
{
    flushall();
    printf ("Enter first name: ");
    gets ((*p1).f_name);
    printf ("Enter last name: ");
    gets ((*p1).l_name);
    printf ("Enter age: ");
    scanf ("%d", &(*p1).age);

    flushall();
    printf ("Enter first name: ");
    gets ((*p2).f_name);
    printf ("Enter last name: ");
    gets ((*p2).l_name);
    printf ("Enter age: ");
    scanf ("%d", &(*p2).age);

    flushall();
    printf ("Enter first name: ");
    gets ((*p3).f_name);
    printf ("Enter last name: ");
    gets ((*p3).l_name);
    printf ("Enter age: ");
    scanf ("%d", &(*p3).age);
}

נניח כי בפונקצית main() יש גם 3 משתנים מסוג person : 3pr1,pr2,pr וכתובותיהם בהתאמה FFD6, FFB6, FF96. כשנעביר את המשתנים לפונקציה get_detailes נרצה שתוכנם של המשתנים pr1,pr2,pr3 ישתנו ולכן נעביר את הכתובת של המשתנים pr1,pr2,pr3 (כי משתנה מטיפוס מבנה מתנהג כמו משתנה מטיפוס פשוט: int, float וכו' ולכן אם נעביר את תוכנו של המשתנה מטיפוס מבנה הארגומנט שנשלח לא ישתנה בעקבות הפונקציה שאליה מעבירים את תוכנו). כעת בפונקציה get_detailes() הפרמטרים הם מטיפוס מצביע מכיוון ששלחנו את הכתובת של המשתנים pr1,pr2,pr3 אז הפרמטרים של הפונקציה get_detailes צריכים להיות משתני כתובת - כלומר מצביעים (שוב - לכל המעוניין לקרוא יותר על נושא זה יכול לעבור לפרק על מבנה נתונים). כעת כל שנחוץ לנו הוא להבין כי לכל משתנה יש כתובת בזיכרון ותוכן, ותוכנו של משתנה מטיפוס מצביע הוא לא ערך פשוט אלא כתובת.
נניח כי p1,p2,p3 בעלי הכתובות FF90,FF92,FF94 בהתאמה (גודלו של משתנה כתובת הוא 2 בתים). אזי ערכם יהיה הכתובות של pr1,pr2,pr3 בהתאמה. כדי לגשת לשדות של המשתניםpr1,pr2,pr3 יש להשתמש באופרטור הכוכבית (מכיוון שהקדימות של אופרטור הנקודה עליונה מקדימותו של אופרטור הכוכבית יש לשים סוגריים מסביב לאופרטור הכוכבית כדי שהוא יתבצע ראשון) - כדי לפנות לשדה f_name של pr1 יש לרשום (*p1).f_name שמשמעו ראה מהו *p1 (שהוא הכתובת של pr1) ופנה לשדה f_name שלו (על ידי האופרטור הנקודה).
כעת נראה את התוכנית במלואה:
דוגמא 1:

#include <conio.h>
#include <stdio.h>

struct person
{
    char f_name[15];
    char l_name[15];
    int age;
};

void get_detailes (struct person* p1, struct person* p2, struct person* p3)
{
    flushall();
    printf ("Enter first name: ");
    gets ((*p1).f_name);
    printf ("Enter last name: ");
    gets ((*p1).l_name);
    printf ("Enter age: ");
    scanf ("%d", &(*p1).age);

    flushall();
    printf ("Enter first name: ");
    gets ((*p2).f_name);
    printf ("Enter last name: ");
    gets ((*p2).l_name);
    printf ("Enter age: ");
    scanf ("%d", &(*p2).age);

    flushall();
    printf ("Enter first name: ");
    gets ((*p3).f_name);
    printf ("Enter last name: ");
    gets ((*p3).l_name);
    printf ("Enter age: ");
    scanf ("%d", &(*p3).age);
}

void print_detailes (struct person p1, struct person p2,struct person p3)
{
    printf ("\n\rThe detailes of the tree persons: \n\r");
    printf ("%s %s %d \n\r", p1.f_name, p1.l_name, p1.age);
    printf ("%s %s %d \n\r", p2.f_name, p2.l_name, p2.age);
    printf ("%s %s %d \n\r", p3.f_name, p3.l_name, p3.age);
}

void main()
{
    struct person pr1, pr2, pr3;
    get_detailes (&pr1, &pr2, &pr3);
    print_detailes (pr1, pr2, pr3);
}
העבר את העכבר מעל הדוגמא כדי לראות הסבר מפורט


מבנה כטיפוס למערך

ניתן ליצור מערך של מבנה בדיוק כמו בהצהרת מערך רגיל רק הטיפוס לא יהיה פשוט כמו int, float וכו' אלא מבנה. לדוגמא:
struct person p[3];
יוצר מערך של שלושה משתנים מטיפוס person .
כדי לגשת לשדותיו יש לפנות קודם לאיבר שאנו מעוניינים בו במערך - לדוגמא, אנו רוצים לבדוק את השדה f_name באיבר הראשון של המערך. נרשום: p[1].f_name

דוגמא 2:

#include <conio.h>
#include <stdio.h>

struct person
{
    char f_name[15];
    char l_name[15];
    int age;
};

void get_detailes (struct person pr[])
{
    int i = 0;
    for ( ; i < 3; i++)
    {
       flushall();
       printf ("%d.Enter first name: ", i+1);
       gets (pr[i].f_name);
       printf ("%d.Enter last name: ", i+1);
       gets (pr[i].l_name);
       printf ("%d.Enter age: ", i+1);
       scanf ("%d", &pr[i].age);
    }
}

void print_detailes (struct person pr[])
{
    int i = 0;
    printf ("\n\rThe detailes of the tree persons: \n\r");

    for ( ; i < 3; i++)
       printf ("%s %s %d \n\r", pr[i].f_name, pr[i].l_name, pr[i].age);
}

void main()
{
    struct person pr[3];
    get_detailes (pr);
    print_detailes (pr);
}
העבר את העכבר מעל הדוגמא כדי לראות הסבר מפורט


מבנה מקונן

שדות המבנה יכולים להיות בעצמם מבנה לדוגמא:
struct address
{
   char city[15];
    char street[15];
};

struct citizen
{
    struct address add;
    long id;
};

נניח כי אנו מצהירים על משתנה מסוג citizen:
struct citizen ctzn;
כדי לגשת לשדה id נרשום: ctzn.id
כדי לגשת לשדה city נרשום ctzn.add.city
כלומר ראשית ניגש לשדה add שהוא גם מטיפוס מבנה וממנו ניגש לשדות שלו שוב על ידי אופרטור הנקודה.

דוגמא 3:

#include <conio.h>
#include <stdio.h>

struct person
{
    char f_name[15];
    char l_name[15];
    int age;
};

struct worker
{
    struct person pr;
    int experience;
};

void get_detailes (struct worker wr[])
{
    int i,j;
    for (i = 0; i < 3; i++)
    {
       flushall();
       printf ("%d.Enter first name: ",i+1);
       gets (wr[i].pr.f_name);
       printf ("%d.Enter last name: ", i+1);
       gets (wr[i].pr.l_name);
       printf ("%d.Enter age: ", i+1);
       scanf ("%d", &wr[i].pr.age);
       printf ("%d.Enter years of experience: ", i+1);
       flushall();
       scanf ("%d", &wr[i].experience);
    }
}

void print_detailes (struct worker wr[])
{
    int i;

    printf ("\n\rThe detailes of the tree persons: ");
    for (i = 0; i < 3; i++)
    {
       printf ("\n\r%s %s ", wr[i].pr.f_name, wr[i].pr.l_name);
       printf ("%d %d ", wr[i].pr.age, wr[i].experience);
    }
}

void main()
{
    struct worker wr[3];
    get_detailes (wr);
    print_detailes (wr);
}
העבר את העכבר מעל הדוגמא כדי לראות הסבר מפורט


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

שמירת מערך מטיפוס מבנה בקובץ

כדי לבצע זאת יש לפתוח את הקובץ לקריאה לאחר שפעולה זו הסתיימה בהצלחה יש לעבור כל כל אברי המערך בלולאה ולהעתיק איבר איבר ושדה שדה לקובץ כך:
for ( ; i < LENG; i++)
{
    fprintf (f, "%s %s ", wr[i].pr.f_name, wr[i].pr.l_name);
    fprintf (f, "%d %d\n\r", wr[i].pr.age, wr[i].experience);
}
נניח שהמערך שלנו הוא מערך של עובדים ושמו wr בעל השדות: f_name, l_name, age, experience שהם שם פרטי,שם משפחה,גיל וניסיון בהתאמה.נדפיס לקובץ שהמצביע שלו הוא f בתחילה את השדות של העובד הראשון wr[0] , נתקדם בלולאה לאיבר הלאה וכך הלאה עד שהגענו לאיבר האחרון wr[LENG-1] כי תנאי הלולאה הוא i < LENG .
כעת נרשום את התוכנית במלואה:
דוגמא 4:

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

#define LENG 3

struct person
{
    char f_name[15];
    char l_name[15];
    int age;
};

struct worker
{
    struct person pr;
    int experience;
};

void get_detailes (struct worker wr[])
{
    int i;
    for (i = 0; i < LENG; i++)
    {
       flushall();
       printf ("%d.Enter first name: ",i+1);
       gets (wr[i].pr.f_name);
       printf ("%d.Enter last name: ", i+1);
       gets (wr[i].pr.l_name);
       printf ("%d.Enter age: ", i+1);
       scanf ("%d", &wr[i].pr.age);
       printf ("%d.Enter years of experience: ", i+1);
       flushall();
       scanf ("%d", &wr[i].experience);
    }
}

void print_detailes (struct worker wr[])
{
    int i;
    printf ("\n\rThe detailes of the persons: ");
    for (i = 0; i < LENG; i++)
    {
       printf ("\n\r%s %s ", wr[i].pr.f_name, wr[i].pr.l_name);
       printf ("%d %d ", wr[i].pr.age, wr[i].experience);
    }
}

void save (struct worker wr[], FILE* f)
{
    char file_name[15];
    int i = 0;

    printf ("\n\rEnter file name to create: ");
    flushall();
    gets (file_name);

    if ((f = fopen (file_name, "wt")) == NULL)
    {
       printf ("\n\rCan't open the file %s ", file_name);
       exit(1);
    }
    else
    {
       printf ("\n\rSaving the data in the file %s ", file_name);

       for ( ; i < LENG; i++)
       {
       fprintf (f, "%s %s ", wr[i].pr.f_name, wr[i].pr.l_name);
       fprintf (f, "%d %d\n\r", wr[i].pr.age, wr[i].experience);
       }
    }
    fclose(f);
    printf ("\n\rThe file has been closed");
}

void main()
{
    FILE * s_f; //sutruct file
    struct worker wr[LENG];

    get_detailes (wr);
    print_detailes (wr);
    save (wr,s_f);
}


מבנה כערך המוחזר על ידי פונקציה

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

תבנית לפונקציה המחזירה ערך מבנה:
struct name_of struct name_of_function(Arguments);

דוגמא 5:

בפונקציתmain יש לולאה הקוראת לפונקציה choise המחזירה תו. תו זה נשלח כארגומנט לפונקציה menu הקוראת לשאר הפונקציות על פי בחירת המשתמש. כל פונקציה שנקראה מבצעת את תפקידה ושוב חוזרים לפונקצית main על מנת לאפשר למשתמש לבצע מספר פעולות בתוכנית אחת. ניתן לצאת מהתוכנית על ידי הקשת Q .
#include <conio.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

#define LENG 10
int leng = 0;

struct person
{
    char f_name[15];
    char l_name[15];
    long id;
};

struct student
{
    struct person pr;
    int grade;
};
struct student get_details()
{
    struct student st;

    flushall(); //we need to clean the buffer evry time we
       //get data from keyboard
    printf ("\n\rEnter first name: ");
    gets (st.pr.f_name);
    printf ("\n\rEnter last name: ");
    gets (st.pr.l_name);
    printf ("\n\rEnter id: ");
    flushall();
    scanf ("%ld", &st.pr.id);
    printf ("\n\rEnter grade: ");
    flushall();
    scanf ("%d", &st.grade);
    return st; //return the student to the fun add_student
}
void add_student (struct student st[],struct student s)
{
    strcpy(st[leng].pr.f_name, s.pr.f_name);
    strcpy(st[leng].pr.l_name, s.pr.l_name);
    st[leng].pr.id = s.pr.id;
    st[leng].grade = s.grade;
    leng++; // update the leng of the array
}
void print_detailes (struct student st[])
{
    int i;
    printf ("\n\rThe detailes of the persons: ");
    for (i = 0; i < leng; i++)
    {
       printf ("\n\r%s %s ", st[i].pr.f_name, st[i].pr.l_name);
       printf ("%ld %d ", st[i].pr.id, st[i].grade);
    }
}

void save (struct student st[], FILE* f)
{
    int i;
    printf ("\n\rThe data will be saved in the file student.txt ");
    flushall();

    if ((f = fopen ("student.txt", "wt")) == NULL)
    {
       printf ("\n\rCan't open the file student.txt ");
       exit(1);
    }
    else
    {
       for (i = 0 ; i < leng; i++)
       {
          fprintf (f, "%s %s ", st[i].pr.f_name, st[i].pr.l_name);
          fprintf (f, "%ld %d\n\r", st[i].pr.id, st[i].grade);
       }
    }
    fclose(f);
    printf ("\n\rThe file has been closed");
}

char choise()
{
    printf ("\n\n\r(A) - add student\n\r");
    printf ("(W) - write data in to file\n\r");
    printf ("(P) - print to screen\n\r");
    printf ("(Q) - quit\n\r");
    flushall();
    return getchar();
}
void quit()
{
    printf ("\n\rEnd of program");
    exit(1);
}

void menu (struct student st[], FILE *f, char c)
{
    switch (c)
    {
       case 'A': add_student (st, get_details());break;
       case 'W': save (st, f); break;
       case 'P': print_detailes (st); break;
       case 'Q': quit(); break;
       default : choise();
    }
}

void main()
{
    char c;
    FILE * f;
    struct student st[LENG];

    printf ("choose char to: \n\r");

    while (c = choise())
    {
       menu(st, f, c);
    }
}



מבחן
© איתן 2003. כל הזכויות שמורות למערכת המידע איתן.