1 of 59

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

תרגול 6: מחרוזות ומצביעים

2 of 59

מחרוזות – Null Terminated Strings

    • מחרוזת היא רצף של תווים.

​

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

​

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

​

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

3 of 59

מחרוזת כמערך

#include <stdio.h>

#define SIZE 10

​

void main() {

char str[SIZE]= {'h','e','l','l','o','\n','\0'};

for (int i = 0; str[i] != '\0'; i++) {

printf("%c", str[i]);

}

}

Output:

hello

4 of 59

הדפסה בעזרת %s

#include <stdio.h>

#define SIZE 10

​

void main() {

char str[SIZE]= {'h','e','l','l','o','\n','\0'};

printf("%s", str);

}

Output:

hello

פורמט %s מדפיס למסך את כל התווים עד שהוא מגיע לתו NULL.

5 of 59

אתחול

#include <stdio.h>

#define SIZE 10

​

void main() {

char str[SIZE]= {'h','e','l','l','o','\n'};

printf("%s", str);

}

Output:

hello

שאר המערך מאותחל לאפסים בכל מקרה.

6 of 59

אתחול מקוצר

#include <stdio.h>

#define SIZE 10

​

void main() {

char str[SIZE]= "hello\n";

printf("%s", str);

}

Output:

hello

שאר המערך מאותחל לאפסים בכל מקרה.

אפשר לאתחל מחרוזת ריקה על ידי "".

7 of 59

עריכה

#include <stdio.h>

​

void main() {

char str[]= "hello";

printf("%s ",str);

str[0] = 'b';

str[4] = 'a';

printf("%s!\n",str);

}

Output:

hello bella!

כמו במערך, אפשר להשמיט את SIZE.

הגודל יחושב אוטומטית ויכלול מקום עבור NULL.

8 of 59

הדפסה בעזרת %s

#include <stdio.h>

​

void main() {

char str[]= "hello bella!";

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

}

Output:

hello bella!

9 of 59

הדפסה בעזרת %s

#include <stdio.h>

​

void main() {

char str[]= "hello bella!";

str[5] = '\0';

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

str[5] = ' ';

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

}

Output:

hello

hello bella!

10 of 59

קלט בעזרת %s

#include <stdio.h>

#define SIZE 5

​

void main() {

char str[SIZE];

scanf("%s", str);

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

}

Output:

1234

1234

פורמט %s מנקה תווים שקופים מתוך stdin.

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

11 of 59

קלט בעזרת %s

#include <stdio.h>

#define SIZE 5

​

void main() {

char str[SIZE];

scanf("%s", str);

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

}

Output:

1234

1234

פורמט %s מנקה תווים שקופים מתוך stdin.

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

גם printf וגם scanf מצפות לקבל מצביע כשמוגדר פורמט %s.

מכיוון שמערך הוא מצביע, אין צורך להוסיף &.

12 of 59

מחרוזות - קלט בעזרת %s

#include <stdio.h>

#define SIZE 5

​

void main() {

char str[SIZE];

scanf("%s", str);

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

}

Output:

12345678

12345678

Output:

123456789

123456789

*** stack smashing detected ***

./a.out terminated

Aborted (core dumped)

13 of 59

קלט בעזרת %s והגבלת [width]

#include <stdio.h>

#define SIZE 5

​

void main() {

char str[SIZE];

scanf("%4s", str);

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

}

Output:

12345678

1234

Output:

123456789

1234

14 of 59

התמודדות עם Magic Number

#include <stdio.h>

#define SIZE 21

char format[5];

​

​

​

void main() {

char str[SIZE];

sprintf(format, "%%%ds", SIZE-1); // "%20s"

scanf(format, str);

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

}

Output:

12345678901234567890123456789

12345678901234567890

הסבר לגבי חישוב הגודל של מחרוזת הפורמט

מספר הספרות של SIZE-1: log10(SIZE-1) + 1

יש עוד שלושה תווים קבועים: "\0", "s", "%".

בסך הכל החישוב הוא: log10(SIZE-1) + 4

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

15 of 59

מחרוזות - קלט בעזרת %s

#include <stdio.h>

#define SIZE 15

​

void main() {

char str[SIZE];

scanf("%s", str);

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

}

Output:

hello bella!

hello

16 of 59

תזכורת

הטיפוס המתאים

specifier

length

תו

%c

l

מחרוזת - הפונקציה תקרא תווים עד שהיא תגיע לתו שקוף (whitespace character).

%s

l

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

%[…]

l

מחרוזת, על פי negated scanset - הפונקציה תקרא תווים כל עוד הם לא תואמים לאחד מהתווים שהוגדרו.

%[^…]

l

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

%n

hh, h, l, ll

כאשר scanf עוצרת את פעולתה בפורמט %s או scanset – התו שעצר אותה נשאר בבאפר.

17 of 59

מחרוזות - קלט בעזרת scanset

#include <stdio.h>

#define SIZE 15

​

void main() {

char str[SIZE];

scanf("%[^\n]", str);

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

}

Output:

hello bella!

hello bella!

פורמט scanset לא מנקה תווים שקופים מתוך stdin.

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

18 of 59

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

void calmDown(char str[]) {

for (int i = 0; str[i] != '\0'; i++) {

if (isUpperCase(str[i])) {

str[i] = toLowerCase(str[i]);

}

}

}

int isUpperCase(char c) {

return 'A' <= c && c <= 'Z';

}

char toLowerCase(char c) {

return c + 'a' - 'A';

}

19 of 59

מחרוזות - קלט בעזרת scanset

#include <stdio.h>

#define SIZE 15

​

void main() {

char str[SIZE];

scanf("%[^\n]", str);

calmDown(str);

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

}

Output:

Hello BELLA!

hello bella!

20 of 59

ספריית <string.h>

21 of 59

חישוב אורך מחרוזת

​

#define SIZE 20

​

void main() {

char str[SIZE];

scanf("%s", str);

int i = 0;

while (str[i] != '\0') {

i++;

}

printf("The length is: %d\n", i);

}

Output:

Hello!

The length is: 6

22 of 59

חישוב אורך מחרוזת - strlen

#include <string.h>

#define SIZE 20

​

void main() {

char str[SIZE];

scanf("%s", str);

int length = strlen(str);

printf("The length is: %d\n", length);

}

Output:

Hello!

The length is: 6

23 of 59

העתקת מחרוזת

​

#define SIZE 10

​

void main() {

char src[] = "Hello";

char dest[SIZE];

int i;

for (i = 0; src[i] != '\0'; i++) {

dest[i] = src[i];

}

dest[i] = '\0';

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

}

Output:

Hello

24 of 59

העתקת מחרוזת - strcpy

#include <string.h>

#define SIZE 10

​

void main() {

char src[] = "Hello";

char dest[SIZE];

strcpy(dest, src);

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

}

Output:

Hello

חייב להיות מספיק מקום בתוך dest לכל התווים המועתקים!

25 of 59

שגיאות נפוצות

#define SIZE 20

​

void main() {

char src[SIZE] = "Hello";

char dest[SIZE];

dest = "Hello";

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

}

Output:

error: assignment to expression with array type

26 of 59

שגיאות נפוצות

#define SIZE 20

​

void main() {

char src[SIZE] = "Hello";

char dest[SIZE] = src;

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

}

Output:

error: invalid initializer

27 of 59

שרשור מחרוזות

void main() {

char dest[SIZE] = "Hello";

char src[SIZE] = "World";

int i = 0, j = 0;

while (dest[i] != '\0') {

i++;

}

while (src[j] != '\0') {

dest[i++] = src[j++];

}

dest[i] = '\0';

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

}

Output:

HelloWorld

28 of 59

שרשור מחרוזות - strcat

#include <string.h>

#define SIZE 20

​

void main() {

char dest[SIZE] = "Hello";

char src[] = "World";

strcat(dest, " ");

strcat(dest, src);

strcat(dest, "!");

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

}

Output:

Hello World!

חייב להיות מספיק מקום בתוך dest לכל התווים המשורשרים!

29 of 59

השוואת מחרוזות

    • בהשוואת מחרוזות (או סתם שני דברים) יש שלוש אפשרויות.

​

    • הראשון שווה לשני.
    • הראשון גדול מהשני.
    • הראשון קטן מהשני.

​

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

​

    • במקרה הראשון נחזיר אפס.
    • במקרה השני נחזיר מספר חיובי.
    • במקרה השלישי נחזיר מספר שלילי.

30 of 59

השוואת מחרוזות

int strcmp_impl(char str1[], char str2[]) {

int i = 0;

while (str1[i] && str2[i]) {

if (str1[i] != str2[i]) {

return str1[i] > str2[i] ? 1 : -1;

}

i++;

}

return str1[i] - str2[i];

}

31 of 59

השוואת מחרוזות

int main() {

char str[] = "aaa";

​

char str1[] = "aaaaa";

char str2[] = "aab";

char str3[] = "bbb";

char str4[] = "aaa";

​

printf("%d\n", strcmp_impl(str, str1));

printf("%d\n", strcmp_impl(str, str2));

printf("%d\n", strcmp_impl(str, str3));

printf("%d\n", strcmp_impl(str, str4));

​

printf("%d\n", strcmp_impl(str1, str));

printf("%d\n", strcmp_impl(str2, str));

printf("%d\n", strcmp_impl(str3, str));

printf("%d\n", strcmp_impl(str4, str));

}

Output:

-97

-1

-1

0

​

97

1

1

0

32 of 59

השוואת מחרוזות - strcmp

#include <string.h>

int main() {

char str[] = "aaa";

char str1[] = "aaaaa";

char str2[] = "aab";

char str3[] = "bbb";

char str4[] = "aaa";

​

printf("%d\n", strcmp(str, str1));

printf("%d\n", strcmp(str, str2));

printf("%d\n", strcmp(str, str3));

printf("%d\n", strcmp(str, str4));

​

printf("%d\n", strcmp(str1, str));

printf("%d\n", strcmp(str2, str));

printf("%d\n", strcmp(str3, str));

printf("%d\n", strcmp(str4, str));

}

Output:

-97

-1

-1

0

​

97

1

1

0

33 of 59

הפונקציה strtok

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

#include <stdio.h>

#include <string.h>

​

void strtok_ex(char str[], char delim[]) {

char* token = strtok(str, delim);

while (token != NULL) {

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

token = strtok(NULL, delim);

}

}

​

34 of 59

הפונקציה strtok

void main () {

char str[] = "Hello, my website is: www.google.com";

strtok_ex(str, ",");

}

Output:

Hello

my website is: www.google.com

35 of 59

הפונקציה strtok

void main () {

char str[] = "Hello, my website is: www.google.com";

strtok_ex(str, ".");

}

Output:

Hello, my website is: www

google

com

36 of 59

הפונקציה strtok

void main () {

char str[] = "Hello, my website is: www.google.com";

strtok_ex(str, " ");

}

Output:

Hello,

my

website

is:

www.google.com

37 of 59

הפונקציה strtok

void main () {

char str[] = "Hello, my website is: www.google.com";

strtok_ex(str, ",. ");

}

Output:

Hello

my

website

is:

www

google

com

38 of 59

הפונקציה strtok

void main () {

char str[] = "Hello, my website is: www.google.com";

strtok_ex(str, ",. :");

}

Output:

Hello

my

website

is

www

google

com

39 of 59

מצביעים

40 of 59

הזיכרון

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

​

    • כל משתנה תופס את גודלו בזיכרון בבתים.

​

​

​

​

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

​

    • ניתן לקבל את כתובתו של משתנה על ידי שימוש באופרטור &.

​

​

1013

1012

1011

1010

1009

1008

1007

1006

1005

1004

1003

1002

1001

1000

int

i

​

​

​

​

short

s

​

​

​

char c

41 of 59

מצביע

    • מצביע הוא משתנה המכיל כתובת.

​

    • כל הכתובות במערכת אחת הן באותו גודל.
      • במערכת 32-bit כל הכתובות יהיו בגודל של 4 בתים.
      • במערכת 64-bit כל הכתובות יהיו בגודל של 8 בתים.

​

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

​

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

type* ptr;

type *ptr;

​

​

42 of 59

מצביע

    • שינוי הערך המוצבע יעשה באמצעות האופרטור *:

*ptr = value;

​

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

​

int* p;

char** ptr;

​

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

​

int *p1, *p2, *p3;

​

43 of 59

דוגמה

int main() {

int x = 1;

int* p1 = &x;

int** p2 = &p1;

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

*p1 = 2;

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

**p2 = 3;

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

x = *p1 + **p2;

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

}

Output:

1

2

3

6

value

identifier

address

1

x

1000

1000

p1

2000

2000

p2

3000

44 of 59

דוגמה

void main() {

int x = 1;

int* p = &x;

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

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

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

printf("&x : %p\n", &x);

printf("*&p: %p\n", *&p);

printf("&*p: %p\n", &*p);

printf("*&x: %d\n", *&x);

// printf("&*x: %d\n", &*x);

}

Output:

x : 1

p : 0x7fffe154773c

*p : 1

&x : 0x7fffe154773c

*&p: 0x7fffe154773c

&*p: 0x7fffe154773c

*&x: 1

45 of 59

הפונקציה swap

void swap(char x, char y) {

char tmp = x;

x = y;

y = tmp;

}

​

void main() {

char x = 'x', y = 'y';

printf("%c %c\n", x, y);

swap(x,y);

printf("%c %c\n", x, y);

}

Output:

x y

x y

46 of 59

הפונקציה swap

void swap(char* x, char* y) {

char tmp = *x;

*x = *y;

*y = tmp;

}

​

void main() {

char x = 'x', y = 'y';

printf("%c %c\n", x, y);

swap(&x,&y);

printf("%c %c\n", x, y);

}

Output:

x y

y x

47 of 59

מה הפלט?

int main() {

unsigned short x = 0xABCD;

unsigned short y = 0x1234;

printf("x: %X y: %X\n", x, y);

swap(&x, &y);

printf("x: %X y: %X\n", x, y);

}

warning: passing argument 1 of 'swap 'from incompatible pointer type

note: expected 'char * 'but argument is of type 'short unsigned int * '

warning: passing argument 2 of 'swap 'from incompatible pointer type

note: expected 'char * 'but argument is of type 'short unsigned int * '

Output:

x: ABCD y: 1234

x: AB34 y: 12CD

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

להרחבה: סדר בתים.

48 of 59

NULL

    • NULL הוא קבוע המסמל "אין ערך" בעולם המצביעים.

​

    • הוא מוגדר על ידי משהו בסגנון של:

#define NULL 0

    • נשתמש בו בהקשר של מצביעים, כמו שנשתמש ב-0 בהקשר של טיפוסים מספריים וב-'\0' בהקשר של טיפוסים טקסטואליים.

​

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

​

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

49 of 59

אריתמטיקת מצביעים

    • ישנם ארבעה אופרטורים מתמטיים שאפשר להפעיל על מצביע.

++, --, +, -

    • האופרטורים פועלים באופן שונה על מצביעים מטיפוסים שונים.

​

​

​

New address

Calculation

Operator

Initial address

Pointer type

1001

1000 + sizeof(char)

p1++

1000

char* p1

2002

2000 + sizeof(short)

p2++

2000

short* p2

3004

3000 + sizeof(int)

p3++

3000

int* p3

4008

4000 + sizeof(long)

p4++

4000

long* p4

50 of 59

מצביעים וקבועים

    • בניגוד למשתנים רגילים, בהגדרת מצביעים יש משמעויות שונות למיקום המילה השמורה const (ביחס לכוכבית).

​

    • מצביע קבוע:

int * const

    • מצביע לקריאה בלבד:

const int *

int const *

    • מצביע קבוע לקריאה בלבד:

const int * const

int const * const

    • שאר האפשרויות לא מתקמפלות:

const * int / * const int / * int const

​

51 of 59

מערכים ומצביעים

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

a

​

​

    • בשל אריתמטיקת מצביעים, a+i הוא מצביע לאיבר ה-i במערך.
      • כי בפועל החישוב הוא a + i * sizeof(type).

​

    • האופרטור [] מתורגם על ידי הקומפיילר באופן הבא:

​

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

​

​

​

​

​

​

​

​

​

​

​

type a[10];

a[i] => *(a+i)

a[0] => *(a+0) => *a

52 of 59

מערכים רב מימדיים

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

type a[SIZE1][SIZE2];

​

a

​

​

    • המצביע הוא מטיפוס מיוחד:

type (*)[SIZE2]

​

    • האופרטור [] מתורגם על ידי הקומפיילר באופן הבא:

a[i][j] => *(a + i*SIZE2+ j)

SIZE1*SIZE2*sizeof(type) bytes

​

53 of 59

מערכים רב מימדיים

    • לדוגמה:

char a[4][3];

    • הטיפוס של המצביע:

char (*)[3]

​

​

​

​

​

​

​

​

​

​

​

​

​

​

​

​

​

​

​

​

​

​

​

​

​

​

​

54 of 59

מערכים רב מימדיים

    • לדוגמה:

char a[4][3];

    • הטיפוס של המצביע:

char (*)[3]

​

​

​

​

​

​

​

​

​

​

​

​

​

​

​

​

​

​

​

​

​

​

​

​

​

​

​

a[0][2] => *(a + 0*3 + 2)

55 of 59

מערכים רב מימדיים

    • לדוגמה:

char a[4][3];

    • הטיפוס של המצביע:

char (*)[3]

​

​

​

​

​

​

​

​

​

​

​

​

​

​

​

​

​

​

​

​

​

​

​

​

​

​

​

a[0][2] => *(a + 0*3 + 2)

a[2][1] => *(a + 2*3 + 1)

56 of 59

מערכים רב מימדיים

    • לדוגמה:

char a[4][3];

    • הטיפוס של המצביע:

char (*)[3]

​

​

​

​

​

​

​

​

​

​

​

​

​

​

​

​

​

​

​

​

​

​

​

​

​

​

​

a[0][2] => *(a + 0*3 + 2)

a[2][1] => *(a + 2*3 + 1)

a[3][0] => *(a + 3*3 + 0)

57 of 59

מצביעים ומערכים

void main() {

int a[5] = {1,2,3,4,5};

int* b = a;

​

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

printf("%d ", a[i]);

}

printf("\n");

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

printf("%d ", b[i]);

}

printf("\n");

}

Output:

1 2 3 4 5

1 2 3 4 5

58 of 59

מצביעים ומשתנים מקומיים

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

​

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

int* bad() {

int x = 1;

return &x;

}

warning: function returns address of local variable

​

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

59 of 59

מצביעים ומשתנים מקומיים

​

void main() {

int* p = bad();

​

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

}

​

void main() {

int* p = bad();

printf("hello world!\n");

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

}

Output:

1

Output:

hello world!

32764