מבוא למדעי המחשב
תרגול 6: מחרוזות ומצביעים
מחרוזות – Null Terminated Strings
מחרוזת כמערך
#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
הדפסה בעזרת %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.
אתחול
#include <stdio.h>
#define SIZE 10
void main() {
char str[SIZE]= {'h','e','l','l','o','\n'};
printf("%s", str);
}
Output:
hello
שאר המערך מאותחל לאפסים בכל מקרה.
אתחול מקוצר
#include <stdio.h>
#define SIZE 10
void main() {
char str[SIZE]= "hello\n";
printf("%s", str);
}
Output:
hello
שאר המערך מאותחל לאפסים בכל מקרה.
אפשר לאתחל מחרוזת ריקה על ידי "".
עריכה
#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.
הדפסה בעזרת %s
#include <stdio.h>
void main() {
char str[]= "hello bella!";
printf("%s\n",str);
}
Output:
hello bella!
הדפסה בעזרת %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!
קלט בעזרת %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 למחרוזת באופן אוטומטי.
קלט בעזרת %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.
מכיוון שמערך הוא מצביע, אין צורך להוסיף &.
מחרוזות - קלט בעזרת %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)
קלט בעזרת %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
התמודדות עם 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
כשנלמד על הקצאה דינאמית נוכל לחשב את הגודל הזה ולהקצות את הזיכרון בזמן ריצה, במקום להגדיר אותו כמשתנה גלובלי.
מחרוזות - קלט בעזרת %s
#include <stdio.h>
#define SIZE 15
void main() {
char str[SIZE];
scanf("%s", str);
printf("%s\n", str);
}
Output:
hello bella!
hello
תזכורת
הטיפוס המתאים | specifier | length |
תו | %c | l |
מחרוזת - הפונקציה תקרא תווים עד שהיא תגיע לתו שקוף (whitespace character). | %s | l |
מחרוזת, על פי scanset - הפונקציה תקרא תווים כל עוד הם תואמים לאחד מהתווים שהוגדרו. | %[…] | l |
מחרוזת, על פי negated scanset - הפונקציה תקרא תווים כל עוד הם לא תואמים לאחד מהתווים שהוגדרו. | %[^…] | l |
מספר התווים שנקראו עד כה בקריאה הנוכחית לפונקציה. | %n | hh, h, l, ll |
כאשר scanf עוצרת את פעולתה בפורמט %s או scanset – התו שעצר אותה נשאר בבאפר.
מחרוזות - קלט בעזרת 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.
לכן אם נרצה לנקות רצף אפשרי כזה, נוסיף רווח מחוץ לפורמט.
העברת מחרוזת לפונקציה
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';
}
מחרוזות - קלט בעזרת 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!
ספריית <string.h>
חישוב אורך מחרוזת
#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
חישוב אורך מחרוזת - 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
העתקת מחרוזת
#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
העתקת מחרוזת - 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 לכל התווים המועתקים!
שגיאות נפוצות
#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
שגיאות נפוצות
#define SIZE 20
void main() {
char src[SIZE] = "Hello";
char dest[SIZE] = src;
printf("%s\n", dest);
}
Output:
error: invalid initializer
שרשור מחרוזות
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
שרשור מחרוזות - 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 לכל התווים המשורשרים!
השוואת מחרוזות
השוואת מחרוזות
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];
}
השוואת מחרוזות
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
השוואת מחרוזות - 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
הפונקציה 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);
}
}
הפונקציה strtok
void main () {
char str[] = "Hello, my website is: www.google.com";
strtok_ex(str, ",");
}
Output:
Hello
my website is: www.google.com
הפונקציה strtok
void main () {
char str[] = "Hello, my website is: www.google.com";
strtok_ex(str, ".");
}
Output:
Hello, my website is: www
com
הפונקציה strtok
void main () {
char str[] = "Hello, my website is: www.google.com";
strtok_ex(str, " ");
}
Output:
Hello,
my
website
is:
www.google.com
הפונקציה strtok
void main () {
char str[] = "Hello, my website is: www.google.com";
strtok_ex(str, ",. ");
}
Output:
Hello
my
website
is:
www
com
הפונקציה strtok
void main () {
char str[] = "Hello, my website is: www.google.com";
strtok_ex(str, ",. :");
}
Output:
Hello
my
website
is
www
com
מצביעים
הזיכרון
1013 | 1012 | 1011 | 1010 | 1009 | 1008 | 1007 | 1006 | 1005 | 1004 | 1003 | 1002 | 1001 | 1000 |
int i | | | | | short s | | | | char c | ||||
מצביע
type* ptr;
type *ptr;
מצביע
*ptr = value;
int* p;
char** ptr;
int *p1, *p2, *p3;
דוגמה
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 |
דוגמה
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
הפונקציה 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
הפונקציה 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
מה הפלט?
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
הבתים שיוחלפו קשורים לאופן שבו נשמרים הערכים בזיכרון.
להרחבה: סדר בתים.
NULL
#define NULL 0
אריתמטיקת מצביעים
++, --, +, -
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 |
מצביעים וקבועים
int * const
const int *
int const *
const int * const
int const * const
const * int / * const int / * int const
מערכים ומצביעים
a
| | | | | | | | | |
|
type a[10];
a[i] => *(a+i)
a[0] => *(a+0) => *a
מערכים רב מימדיים
type a[SIZE1][SIZE2];
a
type (*)[SIZE2]
a[i][j] => *(a + i*SIZE2+ j)
SIZE1*SIZE2*sizeof(type) bytes |
|
מערכים רב מימדיים
char a[4][3];
char (*)[3]
| | | | | | | | | | | |
|
| | |
| | |
| | |
| | |
|
מערכים רב מימדיים
char a[4][3];
char (*)[3]
| | | | | | | | | | | |
|
| | |
| | |
| | |
| | |
|
a[0][2] => *(a + 0*3 + 2)
מערכים רב מימדיים
char a[4][3];
char (*)[3]
| | | | | | | | | | | |
|
| | |
| | |
| | |
| | |
|
a[0][2] => *(a + 0*3 + 2)
a[2][1] => *(a + 2*3 + 1)
מערכים רב מימדיים
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)
מצביעים ומערכים
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
מצביעים ומשתנים מקומיים
int* bad() {
int x = 1;
return &x;
}
warning: function returns address of local variable
מצביעים ומשתנים מקומיים
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