Transcript int
הקצאות דינאמיות
קרן כליף
ביחידה זו נלמד:
מוטיבציה להקצאות דינאמיות
מהי הקצאה דינאמית
יצירת מערך בגודל שאינו ידוע מראש
החזרת מערך מפונקציה
הקצאת מערך של מערכים
הגדלת מערך
הפונקציה strdup
2
© Keren Kalif
מוטיבציה להקצאה דינאמית
בעזרת הקצאה דינאמית נוכל:
3
© Keren Kalif
להקצות מערך בזמן ריצה ,בלי לדעת את גודלו בזמן קומפילציה
נוכל להחזיר מערך מפונקציה
מהי הקצאה דינאמית?
הקצאה דינאמית היא הקצאת שטח זיכרון בגודל מבוקש
בזמן ריצת התוכנית
הקצאה דינאמית מוקצית על שטח הזיכרון heap
4
© Keren Kalif
בניגוד להקצאה סטטית שמוקצית בתחילת התוכנית וגודלה ידוע
כבר בזמן קומפילציה
בניגוד להקצאה סטטית שמוקצית על ה stack -של הפונקציה
ה heap -הוא שטח זיכרון המשותף לכל הפונקציות ,בניגוד ל-
stack
בהקצאת זיכרון דינאמית המתכנת מבקש ממערכת
ההפעלה זיכרון בגודל מסוים המוגדר בבתים ומקבל את
כתובת הבית הראשון בקטע הזיכרון שקיבל
הגדרה נוספת ל<type>* -
אנו יודעים שמשתנה מטיפוס *><typeמכיל כתובת של
משתנה מטיפוס > <typeכלשהו
כתובת התחלה של מערך מטיפוס ><typeהיא גם
כתובתו של האיבר הראשון במערך ,שהוא משתנה
מטיפוס ><type
לכן משתנה מטיפוס *> <typeיכול להכיל גם כתובת
התחלה של מערך
לכן נאמר ש <type>* -הוא פוטנציאל למערך
5
© Keren Kalif
כלומר ,יכול להכיל כתובת התחלה של מערך (ולא רק כתובת
של משתנה יחיד מטיפוס >)<type
הפקודה new
;]int* arr = new int[size
נשתמש בה כאשר נרצה להקצות מערך בגודל שאינו ידוע
בזמן קומפילציה
כדי להקצות מערך נשתמש ב ][ -ובתוכם גודל המערך
המבוקש
הפקודה מחזירה את כתובת ההתחלה של המערך
שהוקצה ובהמשך נשתמש עם כתובת זו
6
© Keren Kalif
7
© Keren Kalif
– הקצאת מערך בגודל שאינו ידועnew
מראש
void main()
{
int size, *arr;
cout << "Please enter the size of the array: ";
cin >> size;
arr = new int[size];
, אין שםheap -נשים לב שלמשתנה על ה
!אלא רק יש אליו הצבעה מאחת הפונקציות
if (!arr) // (arr == NULL) --> allocaton didn't succeed
{
cout << "ERROR! Out of memory!\n";
return;
}
cout << "Values in the array: ";
for (int i=0 ; i < size ; i++)
cout << *(arr+i) << “ “; //*(arr+i) == arr[i]
cout << "\nPlease enter “ << size << “ numbers: “;
for (int i=0 ; i < size ; i++)
cin >> arr[i];
}
cout << "Values in the array: ";
for (int i=0 ; i < size ; i++)
cout << arr[i] << “ “;
cout << endl;
int
???
1
3000
int
???
5
3004
int
???
8
3008
heap -זיכרון ה
int:size
???
3
1000
int*: arr
3000
???
1004
???
1008
int: i
main -הזיכרון של ה
שחרור הזיכרון שהוקצה
אחריות המתכנת לשחרר את כל זיכרון שהוקצה דינאמית
במילים אחרות ,המתכנת אחראי להחזיר למערכת
ההפעלה כל שטח זיכרון שביקש ממנה בזמן ריצה
הסיבה:
8
© Keren Kalif
שטח ה heap -עליו מוקצות ההקצאות הדינאמיות מוגבל
בשטחו ומשותף לכל התוכניות ,ואז התוכנית הבאה שתבקש
זיכרון עלולה לקבל NULLכי אנחנו לא החזרנו את הזיכרון
שביקשנו בסיום העבודה...
צריך לזכור :כאשר יש סביבת עבודה משותפת ,צריך להתחשב
גם באחרים (וזה שיעור חשוב בכלל לחיים ;))-
הקומפיילר לא מתריע על אי-שחרור הזיכרון ואין שום
אינדיקציה לדעת זאת ,לכן חייבים לשים לב!!
פונקציה לשחרור הקצאות דינאמיות
הפונקציה deleteמשחררת הקצאה דינאמית
כאשר ההקצאה היא למערך צריך לזכור לשים [] לפני שם
המערך
delete [] arr
9
© Keren Kalif
void main()
{
int size, *arr;
– שחרור זיכרוןdelete
cout << "Please enter the size of the array: ";
cin >> size;
arr = new int[size];
if (!arr) // (arr == NULL) --> allocaton didn't succeed
{
cout << "ERROR! Out of memory!\n";
return;
}
cout << "Values in the array: ";
for (int i=0 ; i < size ; i++)
cout << *(arr+i) << “ “; //*(arr+i) == arr[i]
cout << "\nPlease enter “ << size << “ numbers: “;
for (int i=0 ; i < size ; i++)
cin >> arr[i];
}
cout << "Values in the array: “;
for (int i=0 ; i < size ; i++)
cout << arr[i] << “ “;
cout << endl;
delete []arr;
int
???
1
3000
int
???
5
3004
int
???
8
3008
heap -זיכרון ה
int:size
???
3
1000
int*: arr
3000
???
1004
???
1008
int: i
main -הזיכרון של ה
10
© Keren Kalif
תזכורת :החזרת מערך מפונקציה
ראינו כי כאשר מעבירים מערך לפונקציה ,מעבירים רק
את כתובת ההתחלה שלו ,ולא עותק של כל המערך
ובאופן דומה ,כאשר מחזירים מערך שהוגדר בפונקציה,
חוזרת כתובת ההתחלה שלו ,ולא עותק של כל המערך
הבעייתיות :כאשר יוצאים מהפונקציה שטח הזיכרון שלה
משתחרר ויש לנו מצביע לזיכרון שנמחק...
הפתרון :הקצאה דינאמית
11
© Keren Kalif
12
© Keren Kalif
הבעייתיות בהחזרת מערך:תזכורת
דוגמא- מפונקציה
const int SIZE = 3;
int[]: arr
???
3
2000
int* readArray()
???
5
2004
{
int arr[SIZE];
???
6
2008
cout << "Please enter “ << SIZE << “ numbers: “;
???
3
int: i
2012
for (int i=0 ; i < SIZE ; i++)
readArray הזיכרון של
cin >> arr[i];
return arr;
:warning הקומפיילר נותן
}
void main()
{
int* arr;
arr = readArray();
}
returning address of local variable or temporary
שפירושה שאנחנו מחזירים כתובת למשתנה בזיכרון שישתחרר
cout << "The array is: \n";
for (int i=0 ; i < SIZE ; i++)
cout << arr[i] << “ “;
cout << endl;
לעולם לא נחזיר מפונקציה
כתובת של משתנה
!שהוגדר בה מקומית
int*: arr
2000
???
1000
int: i
???
1004
main -הזיכרון של ה
const int SIZE = 3;
הקצאת:הפתרון
המערך דינאמית
int* buildArray()
{
int* arr = new int[SIZE];
if (!arr)
{
cout << "ERROR! Not enough memory!\n";
return;
}
for (int i=0 ; i < SIZE; i++)
arr[i] = i+1;
}
אם אנחנו יוצאים מפונקציה שהקצתה
חובה להחזיר את,דינאמית ולא שחררה
כדי שנוכל,כתובת ההתחלה של ההקצאה
!לשחרר אותה בהמשך
אחרת כאשר נצא מהפונקציה כבר לא יהיה
!משתנה שיכיל את מיקום ההקצאה
return arr;
int:i
void main()
{
int *arr, i;
int*: arr
}
delete []arr;
2000
3000
???
2004
buildArray הזיכרון של
arr = buildArray();
cout << "Values in the array: "; int
for (i=0 ; i < SIZE ; i++)
int
cout << arr[i] << “ “;
cout << endl;
int
???
???
1
3000
???
2
3004
???
3
3008
heap -זיכרון ה
int*: arr
int: i
3000
???
1000
???
1004
main -הזיכרון של ה
13
© Keren Kalif
הקצאה בתוך פונקציה
אחריות שלנו כמתכנתים לשחרר את כל הזיכרון שהקצינו
יש לשים לב בייחוד במקרים בהם ההקצאה מתבצעת
בפונקציה אחת ,והשחרור צריך להיות בפונקציה אחרת!
14
© Keren Kalif
החזרת מערך מפונקציה by pointer
למדנו שפונקציה יכולה להחזיר ערכים ע"י קבלת כתובת
לעדכון התשובה
למשל:
;)int getSum(int arr[], int size
לעומת:
;)void getSum(int arr[], int size, int* sum
באותו אופן ניתן גם להחזיר מערך שייוצר בתוך הפונקציה
15
© Keren Kalif
לא לשכוח להעביר את המערך כ** -
void buildArray(int* arr, int size)
{
arr = new int[size];
החזרת מערך מפונקציה
דוגמא- by pointer
if (!arr)
}
cout << "ERROR! Not enough memory!\n”;
return;
{
int
???
1
3000
int
???
2
3004
int
???
3
3008
for (int i=0 ; i < size ; i++)
arr[i] = i+1;
heap -זיכרון ה
{
void main()
{
int size, *arr=NULL;
int:size
int: i
cout << "Please enter the size of the array: “;
cin >> size;
buildArray(arr, size);
cout << "Values in the array: “;
for (int i=0 ; i < size ; i++)
cout << arr[i] << “ “;
cout << endl;
}
delete []arr;
int*: arr
2000
???
2004
NULL
3000
2008
buildArray הזיכרון של
int:size
???
3
1000
int*: arr
NULL
???
1004
???
1008
int: i
...פה התוכנית תעוף
3
main -הזיכרון של ה
16
© Keren Kalif
void buildArray(int** arr, int size)
}
*arr = new int[size];
החזרת מערך מפונקציה
התיקון- by pointer
if (!*arr)
}
cout << "ERROR! Not enough memory!\n”;
return;
{
int
???
1
3000
int
???
2
3004
int
???
3
3008
for (int i=0 ; i < size ; i++)
(*arr)[i] = i+1;
heap -זיכרון ה
{
void main()
{
int size, *arr=NULL;
int:size
int: i
int**: arr
cout << "Please enter the size of the array: “;
cin >> size;
buildArray(&arr, size);
cout << "Values in the array: “;
for (int i=0 ; i < size ; i++)
cout << arr[i] << “ “;
cout << endl;
}
delete []arr;
3
2000
???
2004
1004
2008
buildArray הזיכרון של
int:size
???
3
1000
int*: arr
NULL
3000
???
1004
???
1008
int: i
main -הזיכרון של ה
17
© Keren Kalif
החזרת מערך מפונקציה by ref
למדנו שפונקציה יכולה להחזיר ערכים ע"י קבלת פרמטר
by refשיעדכן את התשובה
למשל:
;)int getSum(int arr[], int size
לעומת:
;)void getSum(int arr[], int size, int& sum
באותו אופן ניתן גם להחזיר מערך שייוצר בתוך הפונקציה
18
© Keren Kalif
לא לשכוח להעביר את המערך כ*& -
void buildArray(int*& arr, int size)
}
arr = new int[size];
החזרת מערך מפונקציה
התיקון- by ref
if (!arr)
}
cout << "ERROR! Not enough memory!\n”;
return;
{
int
???
1
3000
int
???
2
3004
int
???
3
3008
for (int i=0 ; i < size ; i++)
arr[i] = i+1;
heap -זיכרון ה
{
void main()
{
int size, *arr=NULL;
cout << "Please enter the size of the array: ";
cin >> size;
buildArray(arr, size);
cout << "Values in the array: “;
for (int i=0 ; i < size ; i++)
cout << arr[i] << “ “;
cout << endl;
}
delete []arr;
int:size
int: i
3
2000
???
2004
int*: arr
buildArray הזיכרון של
int:size
???
3
1000
int*: arr
NULL
3000
???
1004
???
1008
int: i
main -הזיכרון של ה
19
© Keren Kalif
הקצאת מערך של מערכים ()1
20
© Keren Kalif
כעת אנחנו יכולים לייצר מטריצה שבכל שורה יש מספר
שונה של איברים
21
© Keren Kalif
)2( הקצאת מערך של מערכים
void main()
{
int rows, **matrix, *sizes;
cout << "Enter number of rows in the matrix: ";
cin >> rows;
matrix = new int*[rows];
sizes = new int[rows];
}
1000
???
3000
1004
int: i
???
0
1
2
3
1008
int: j
???
1012
int**: matrix
for ( int i=0; i < rows ; i++ )
{
cout << "Enter size of row #“ << i+1 << “: ”;
cin >> sizes[i];
matrix[i] = new int[sizes[i]];
for (int j=0 ; j < sizes[i] ; j++)
matrix[i][j] = (i+1)*10+j+1;
}
int* 4050
cout << "The matrix is:\n”;
??? 3000
for (int i=0 ; i < rows ; i++)
int* 5000
??? 3004
{
for (int j=0 ; j < sizes[i] ; j++) int*
2200
??? 3008
cout << matrix[i][j] << “ “;
cout << endl;
int
31
0
}
delete []sizes;
int
32
0
for ( int i=0 ; i < rows ; i++ )
int
33
0
delete []matrix[i];
delete []matrix;
int
???
3
int:rows
34
0
4000
??? 1016
main -הזיכרון של ה
int*: sizes
int
11
0
4050
int
12
0
4054
2200
2224
2228
2232
int
21
0
5000
int
22
0
5004
int
23
0
5008
int
???
2
4000
int
???
3
4004
int
???
4
4008
heap -זיכרון ה
הגדלת מערך
בדוגמא הבאה אנו קולטים מהמשתמש מספרים לתוך
מערך ,לא ידוע כמה איברים המשתמש יכניס
כל פעם כאשר כבר אין מקום במערך צריך להגדיל אותו
פי 2
האלגוריתם:
קרא את האיבר החדש ,אם -1צא
אם אין מקום במערך ,הקצה מערך גדול פי 2
22
© Keren Kalif
העתק למערך החדש את האיברים מהמערך הישן
שחרר את המערך המקורי
שנה את מצביע המערך להצביע למערך החדש
הגדלת מערך -הפלט
23
© Keren Kalif
void main()
{
int num, physSize = 2, logicSize = 0;
int *arr = new int[physSize], *tmp;
}
הגדלת מערך – הקוד
cout << "Please enter numbers, -1 to stop:\n”;
while (1)
7
0
int
2200
{
4
0
int
cin >> num;
if (num == -1)
0
7
int
4300
break;
if (physSize == logicSize)
0
4
int
4304
{
0
5
physSize *= 2;
int
4308
tmp = new int[physSize];
0
int
4312
for (int i=0 ; i < logicSize ; i++)
tmp[i] = arr[i];
delete []arr;
heap -זיכרון ה
arr = tmp;
int: num
cout << "Doubled the array size to “ << physSize << endl;
-1
7
5
???
4
1000
1000
}
int: i
??? 1004
1004
cout << "Read number is “ << num << endl;
arr[logicSize] = num;
int: physSize
2
4
???
1008
1008
logicSize++;
}
int: logicSize
1
2
3
???
0
1012
1012
cout << "The array has “ << logicSize
int*: arr
<< “ elements (physSize=“ << physSize << “):\n”;
2200
4300
??? 1016
1016
for (int i=0 ; i < logicSize ; i++)
int*: tmp
4300
??? 1020
1020
cout << arr[i] << “ “;
cout << endl;
main -הזיכרון של ה
delete []arr;
24
© Keren Kalif
?מה יהיה פלט התוכנית הבאה
void main()
{
char* c = new char[5];
char** s = new char*[3];
int i;
for( i=0 ; i < 3 ; i ++ )
{
cout << "enter word : “;
cin.getline(c, 5);
s[i] = c;
{
for(i=0; i<3; i++)
cout << "strings : “ << s[i] << endl;
}
delete []c;
delete []s;
char
???
‘h’
‘b’
‘g’
4300
char
???
‘o’
‘y’
‘i’
4301
char
???
0
‘o’
‘e’
4302
char
???
0
‘d’
4303
char
???
0
4304
char*
???
4300
5300
char*
4300
???
5304
char*
???
4300
5308
heap -זיכרון ה
char*: c
4300
???
1000
5300
???
1004
???
0
1
2
3
1008
char**: s
int: i
main -הזיכרון של ה
25
© Keren Kalif
התיקון לתוכנית הקודמת
void main()
{
char* c;
char** s = new char*[2];
int i;
for( i=0 ; i < 2 ; i ++ )
{
c = new char[5];
cout << "enter word : ";
cin.getline(c, 5);
s[i] = c;
{
for(i=0; i<2; i++)
cout << "strings : “ << s[i] << endl;
}
for (i=0 ; i < 2 ; i++)
delete []s[i];
delete []s;
char
???
‘b’
6300
char
???
‘y’
6301
char
???
‘e’
6302
char
???
0
6303
char
???
6304
char
???
‘h’
4300
char
???
‘i’
4301
char
???
0
4302
char
???
4303
char
???
4304
char*
???
4300
5300
char*
???
6300
5304
heap -זיכרון ה
char*: c
4300
6300
???
1000
5300
???
1004
???
0
1
2
1008
char**: s
int: i
main -הזיכרון של ה
26
© Keren Kalif
הפונקציה strdup
)char* strdup(const char *str
מקבלת מחרוזת ומחזירה העתק שלה:
27
© Keren Kalif
מקצה דינאמית על ה heap -מערך של תווים בגודל המחרוזת
המקורית ,מעתיקה אליו את התוכן ומחזירה את כתובת
ההתחלה שלו
תחזיר NULLבמידה וההקצאה נכשלה
אחריות המתכנת לשחרר את המחרוזת שחזרה!!
28
© Keren Kalif
– דוגמאstrdup
void main()
}
char str1[] = "hi";
char* str2 = "bye";
char* newStr1 = strdup(str1);
char* newStr2 = strdup(str2);
char
‘b’
5300
char
‘y’
5301
char
‘e’
5302
char
‘\0’
5303
static storage -זיכרון ה
cout << "The first duplicated string: |” << newStr1 << “|\n”;
cout << "The second duplicated string: |” << newStr2 << “|\n”;
delete []newStr1;
delete []newStr2;
{
char
‘b’
char
‘y’
char
‘e’
char
‘\0’
6300
6301
6302
6303
???
‘h’
1000
???
‘i’
1001
‘\0’
???
1002
char*: str2
5300
???
1003
char*: newStr1
4300
???
1007
char*: newStr2
6300
???
1011
char: str1[]
char
‘h’
4300
char
‘i’
4301
char
‘\0’
4302
heap -זיכרון ה
main -הזיכרון של ה
ביחידה זו למדנו:
מוטיבציה להקצאות דינאמיות
מהי הקצאה דינאמית
יצירת מערך בגודל שאינו ידוע מראש
החזרת מערך מפונקציה
הקצאת מערך של מערכים
הגדלת מערך
הפונקציה strdup
29
© Keren Kalif
תרגיל 1
כתוב תוכנית המבקשת מהמשתמש להכניס את כמות
המספרים שהוא רוצה שיהיו במערך ,והקצה מערך
בהתאם
הגרל ערכים למערך והגרל מספר נוסף (בטווח )0-9
ייצר מערך חדש המכיל את האינדקסים במערך המקורי
שערך האיבר שבתוכם שווה למספר הנוסף שהתקבל
הצג את המערך שחדש שייצרת
דוגמא:
עבור המערך 1,2,5,2,2,9גודלו 6והמספר ,2יוחזר
מערך בגודל 3שערכיו 1,3,4
30
© Keren Kalif
תרגיל 2
כתוב פונקציה המקבלת מחרוזת ותו .הפונקציה תייצר
ותחזיר מחרוזת חדשה שאורכה ככמות הפעמים שהתו
מופיעה במחרוזת המקורית והיא תכיל תו זה כמספר
פעמים זה
דוגמאות:
31
© Keren Kalif
עבור המחרוזת ” “helloוהתו ’ ‘lתוחזר המחרוזת ”“ll
עבור המחרוזת ” “kerenוהתו ’ ‘eתוחזר המחרוזת ”“ee
תרגיל 3
כתוב פונקציה המקבלת 2מערכים וגודלם .הפונקציה
תחזיר מערך חדש המכיל את הערכים שבשני המערכים
הפוקנציה גם תחזיר את גודל המערך המוחזר
דוגמא:
עבור המערך 1,8,2וגודלו 3והמערך 9,2,6,7וגודלו 4
יוחזר המערך החדש 1,8,2,9,2,6,7וגודלו 7
32
© Keren Kalif