1 of 39

מבוא למדעי המחשב

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

תודה מיוחדת לאייל דיין על מצגות התרגול

1

2 of 39

הגדרת טיפוס - typedef

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

typedef existing-type new-type;

    • למשל:

typdef int bool;

bool isLegal; // equal to: int isLegal;

    • עדיין ניתן להשתמש בטיפוס int.

    • השימוש במשתנה מטיפוס bool זהה לשימוש בטיפוס int.

תודה מיוחדת לאייל דיין על מצגות התרגול

2

3 of 39

מבנים

תודה מיוחדת לאייל דיין על מצגות התרגול

3

4 of 39

מבנים - Structures

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

struct struct-name {

type1 field1-name;

type2 field2-name;

};

    • הגדרת משתנה מהטיפוס החדש תתבצע כך:

struct struct-name var-name;

תודה מיוחדת לאייל דיין על מצגות התרגול

4

5 of 39

מבנים – Structures – דוגמא

struct Point {

int x;

int y;

};

    • הגדרה של משתנה מהטיפוס החדש ושימוש בו יתבצעו כך:

int main() {

struct Point p1;

p1.x = 100;

p1.y = 200;

printf("(%d, %d)\n", p1.x, p1.y);

return 0;

}

תודה מיוחדת לאייל דיין על מצגות התרגול

5

Output:

(100, 200)

6 of 39

מבנים - Structures

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

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

תודה מיוחדת לאייל דיין על מצגות התרגול

6

7 of 39

מבנים - Structures

struct Point {

int x, y;

};

void main () {

struct Point p1 = {1,2};

struct Point p2;

scanf("%d%d", &p2.x, &p2.y);

printf("p1: {x:%d, y:%d}\n", p1.x, p1.y);

printf("p2: {x:%d, y:%d}\n", p2.x, p2.y);

p2 = p1;

printf("p1: {x:%d, y:%d}\n", p1.x, p1.y);

printf("p2: {x:%d, y:%d}\n", p2.x, p2.y);

}

תודה מיוחדת לאייל דיין על מצגות התרגול

7

Output:

3 4

p1: {x:1, y:2}

p2: {x:3, y:4}

p1: {x:1, y:2}

p2: {x:1, y:2}

8 of 39

מבנים - Structures

    • ניתן להשתמש ב-typedef כדי לחסוך את השימוש במילה struct בכל פעם שמצהירים על משתנה כזה.

typedef struct struct-name struct-name;

    • הגדרת משתנה מהטיפוס החדש תתבצע כך:

struct-name var-name;

תודה מיוחדת לאייל דיין על מצגות התרגול

8

9 of 39

מבנים - Structures

    • ניתן לשלב את הקיצור typedef בעת ההגדרה של המבנה.

typedef struct {

type1 field1-name;

type2 field2-name;

} struct-name;

    • הגדרת משתנה מהטיפוס החדש תתבצע כך:

struct-name var-name;

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

תודה מיוחדת לאייל דיין על מצגות התרגול

9

10 of 39

מבנים - Structures

int main() {

Line l;

scanf("%d%d%d%d", &l.p1.x, &l.p1.y, &l.p2.x, &l.p2.y);

l.len = sqrt(pow(l.p1.x-l.p2.x, 2) + pow(l.p1.y-l.p2.y, 2));

printf("The line length is: %.2f\n", l.len);

}

תודה מיוחדת לאייל דיין על מצגות התרגול

10

Output:

0 0 3 4

The line length is: 5.00

typedef struct {

Point p1,p2;

double len;

} Line;

typedef struct {

int x,y;

} Point;

 

11 of 39

מערך מבנים

    • ניתן להגדיר מערך של מבנים:

Line lines[10];

    • כל אחד מאיברי המערך הוא מבנה:

lines[i].p1.x = val;

val = lines[i].p2.y;

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

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

תודה מיוחדת לאייל דיין על מצגות התרגול

11

12 of 39

מצביע למבנה

    • הגדרת מצביע למבנה היא פעולה נפוצה:

Point p;

Point* ptr = &p;

    • הגישה לשדות בעזרת המצביע מתבצעת כרגיל, אך יש להוסיף סוגריים משום שהאופרטור '.' קודם לאופרטור '*':

(*ptr).x = val;

val = (*ptr).x;

    • ניתן לגשת לשדות באמצעות המצביע באופן מקוצר:

ptr->x = val;

val = ptr->x;

תודה מיוחדת לאייל דיין על מצגות התרגול

12

13 of 39

העברת מבנה לפונקציה

    • העברת מבנה לפונקציה מתבצעת by value.
      • כל השדות (כולל מערכים) מועתקים בשלמותם לפרמטר של הפונקציה.

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

    • העברת מערך של מבנים מתבצעת כרגיל, בעזרת מצביע.

תודה מיוחדת לאייל דיין על מצגות התרגול

13

14 of 39

העברת מבנה לפונקציה

#include <stdio.h>

typedef struct {

int x;

int* p;

int arr[1];

} Simple;

void printSimple(const Simple* s) {

printf("s.x: %d\n", s->x);

printf("*(s.p): %d\n", *(s->p));

printf("s.arr[0]: %d\n", s->arr[0]);

}

תודה מיוחדת לאייל דיין על מצגות התרגול

14

arr[0]

x

arr

p

15 of 39

העברת מבנה לפונקציה

void update(Simple s) {

s.x = 10;

*(s.p) = 20;

s.arr[0] = 30;

}

void realUpdate(Simple* s) {

s->x = 100;

*(s->p) = 200;

s->arr[0] = 300;

}

תודה מיוחדת לאייל דיין על מצגות התרגול

15

16 of 39

העברת מבנה לפונקציה

void main() {

Simple s;

s.x = 1;

int local = 2;

s.p = &local;

s.arr[0] = 3;

printSimple(&s);

update(s);

printSimple(&s);

realUpdate(&s);

printSimple(&s);

}

תודה מיוחדת לאייל דיין על מצגות התרגול

16

Output:

s.x: 1

*(s.p): 2

s.arr[0]: 3

s.x: 1

*(s.p): 20

s.arr[0]: 3

s.x: 100

*(s.p): 200

s.arr[0]: 300

17 of 39

מבנים - תרגיל

    • כתבו תוכנית שמנהלת רשימה של 10 עובדים
    • התוכנית מדפיסה באופן חוזר תפריט המאפשר שתי פעולות על הרשימה ויציאה מהתוכנית (סיום התוכנית):
              • 0. יציאה
              • 1. הוספת עובד נוסף לרשימה
              • 2. הדפסת כל העובדים שכרגע ברשימה

תודה מיוחדת לאייל דיין על מצגות התרגול

17

18 of 39

מבנים - תרגיל

struct SEmployee {

char job[30];

int salary;

int seniority;

};

typedef struct SEmployee Employee_t;

void addEmployee(Employee_t employees[], int size, int* currentAmount);

void printList(Employee_t employees[], int currentAmount);

תודה מיוחדת לאייל דיין על מצגות התרגול

18

19 of 39

מבנים - תרגיל

#define SIZE 10

int main() {

char choice;

Employee_t employees[SIZE];

int currentAmount = 0;

do {

printf(…); /* here we print the menu */

scanf(" %c", &choice);

switch(choice) {

case '1':

addEmployee(employees, SIZE, &currentAmount);

break;

case '2':

printList(employees, currentAmount);

break;

}

} while (choice != '0');

}

תודה מיוחדת לאייל דיין על מצגות התרגול

19

20 of 39

מבנים - תרגיל

void addEmployee(Employee_t employees[], int size, int* currentAmount) {

int insertingIndex = *currentAmount;

if (insertingIndex >= size) {

printf("Error! Too many employees.\n");

return;

}

printf("Enter job: ");

scanf(" %[^\n]", employees[insertingIndex].job);

printf("Enter salary: ");

scanf("%d", &employees[insertingIndex].salary);

printf("Enter seniority: ");

scanf("%d", &employees[insertingIndex].seniority);

(*currentAmount)++;

}

תודה מיוחדת לאייל דיין על מצגות התרגול

20

21 of 39

מבנים - תרגיל

void printList(Employee_t employees[], int currentAmount) {

for (int i = 0; i < currentAmount; ++i) {

printf("Job: %s, ", employees[i].job);

printf("Salary: %d, ", employees[i].salary);

printf("Seniority: %d\n", employees[i].seniority);

}

}

תודה מיוחדת לאייל דיין על מצגות התרגול

21

22 of 39

רשימה מקושרת

תודה מיוחדת לאייל דיין על מצגות התרגול

22

23 of 39

רשימה מקושרת

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

תודה מיוחדת לאייל דיין על מצגות התרגול

23

char name[12];

char id[10];

student_t *next;

char name[12];

char id[10];

student_t *next;

char name[12];

char id[10];

student_t *next;

main(){

student_t* head;

}

NULL

24 of 39

רשימה מקושרת

#include <string.h>

struct SStudent {

char name[12];

struct SStudent* next;

};

typedef struct SStudent Student_t;

תודה מיוחדת לאייל דיין על מצגות התרגול

24

25 of 39

רשימה מקושרת

#include <stdio.h>

void main() {

Student_t student1, student2, student3;

student1.name = "Nadav Doron";

student2.name = "Reut Asraf";

student3.name = "Lidor Elias";

student1.next = &student2;

student2.next = &student3;

student3.next = NULL;

printf("%s\n", student1.name);

printf("%s\n", student1.next->name);

printf("%s\n", student1.next->next->name);

}

תודה מיוחדת לאייל דיין על מצגות התרגול

25

Output:

Nadav Doron

Reut Asraf

Lidor Elias

26 of 39

רשימה מקושרת

#include <stdio.h>

void main() {

/* creating structs and assigning their fields

just like the previous slide */

Student_t* current = &student1;

while (current != NULL) {

printf("%s\n", current->name);

current = current->next;

}

}

תודה מיוחדת לאייל דיין על מצגות התרגול

26

Output:

Nadav Doron

Reut Asraf

Lidor Elias

27 of 39

מצביעים לפונקציות

תודה מיוחדת לאייל דיין על מצגות התרגול

27

28 of 39

מערכים ומצביעים (תזכורת)

    • בהגדרת מערך מוגדרים שני דברים באופן אוטומטי.

    • שטח הזיכרון לאחסון איברי המערך.

    • מצביע קבוע שמאותחל בכתובתו של האיבר הראשון.

    • שם המערך הוא למעשה המצביע הזה.

תודה מיוחדת לאייל דיין על מצגות התרגול

28

29 of 39

מערך

type a[4];

type* b = a;

    • הביטויים הבאים שקולים:

a[i]

b[i]

תודה מיוחדת לאייל דיין על מצגות התרגול

29

memory of a

a

b

30 of 39

פונקציה (תזכורת)

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

return-type function-name (type arg-name, ) {

statement;

}

    • חתימה של פונקציה מוגדרת כך:

return-type function-name (type, );

תודה מיוחדת לאייל דיין על מצגות התרגול

30

31 of 39

פונקציה

    • בהגדרת פונקציה מוגדרים שלושה דברים באופן אוטומטי.

    • שטח הזיכרון לאחסון הקוד של הפונקציה.
      • קוד של פונקציה נשמר במקטע הזיכרון Code / Text.

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

    • שם הפונקציה הוא למעשה המצביע הזה.

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

תודה מיוחדת לאייל דיין על מצגות התרגול

31

32 of 39

פונקציה

type f(int x) {

}

type (*g)(int) = f;

    • הביטויים הבאים שקולים:

f(x);

g(x);

תודה מיוחדת לאייל דיין על מצגות התרגול

32

f

g

code of f

33 of 39

מצביע לפונקציה

    • ניתן להגדיר משתנה מטיפוס "מצביע לפונקציה".

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

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

תודה מיוחדת לאייל דיין על מצגות התרגול

33

34 of 39

מצביע לפונקציה

    • חתימה של פונקציה מוגדרת כך:

return-type function-name (type, …);

    • הגדרת מצביע לפונקציה מתבצעת באופן הבא:

return-type (*function-name-ptr) (type, …);

      • מצד שמאל הטיפוס, ומצד ימין שם המשתנה.

תודה מיוחדת לאייל דיין על מצגות התרגול

34

35 of 39

דוגמה

int max(int x, int y) {

if (x > y) {

return x;

} else {

return y;

}

}

תודה מיוחדת לאייל דיין על מצגות התרגול

35

int min(int x, int y) {

if (x < y) {

return x;

} else {

return y;

}

}

#include <stdio.h>

void main() {

int x;

int (*f)(int,int);

f = min;

x = f(1,2);

printf("%d\n", x);

f = max;

x = f(1,2);

printf("%d\n", x);

}

Output:

1

2

36 of 39

מצביע גנרי

    • ניתן להגדיר מצביע מטיפוס גנרי:

void* p;

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

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

    • מכיוון שיש לו כתובת, ניתן להפעיל עליו את האופרטור '&'.
      • ולכן ניתן להגדיר מצביע גנרי מכל רמה.
      • על מצביעים כאלה ניתן להפעיל אופרטור '*', משום שהם מצביעים על מצביע.

תודה מיוחדת לאייל דיין על מצגות התרגול

36

37 of 39

פונקציה גנרית

תודה מיוחדת לאייל דיין על מצגות התרגול

37

void print_int(int* p) {

printf("%d\n", *p);

}

void print_int(void* p) {

printf("%d\n", *(int*)p);

}

void print_string(char* p) {

printf("%s\n", p);

}

void print_char(char* p) {

printf("%c\n", *p);

}

void print_char(void* p) {

printf("%c\n", *(char*)p);

}

void print_string(void* p) {

printf("%s\n", (char*)p);

}

38 of 39

פונקציה גנרית

תודה מיוחדת לאייל דיין על מצגות התרגול

38

void print_int(int* p) {

printf("%d\n", *p);

}

void print_int(void* p) {

printf("%d\n", *(int*)p);

}

void print_string(char* p) {

printf("%s\n", p);

}

void print_char(char* p) {

printf("%c\n", *p);

}

void print_char(void* p) {

printf("%c\n", *(char*)p);

}

void print_string(void* p) {

printf("%s\n", (char*)p);

}

39 of 39

פונקציה גנרית

תודה מיוחדת לאייל דיין על מצגות התרגול

39

void main() {

char c = 'A';

int x = 250;

char s[] = "hello";

generic_print(&c, 2, print_char);

generic_print(&x, 5, print_int);

generic_print(s, 7, print_string);

}

void generic_print(void* p, int n, void(*print)(void*)) {

for (int i = 0; i < n; i++) {

print(p);

}

}

Output:

A

A

250

250

250

250

250

hello

hello

hello

hello

hello

hello

hello