Biến tĩnh vs. Biến động
▪ Một số hạn chế có thể gặp phải khi sử dụng các biến tĩnh:
o Cấp phát ô nhớ dư, gây ra lãng phí ô nhớ.
o Cấp phát ô nhớ thiếu, chương trình thực thi bị lỗi.
▪ Biến động:
o Khi chạy chương trình, kích thước của biến, vùng nhớ và địa chỉ vùng nhớ được cấp phát cho
biến có thể thay đổi.
o Sau khi sử dụng xong có thể giải phóng để tiết kiệm chỗ trong bộ nhớ.
▪ Variable: Biến là một ô nhớ đơn lẻ hoặc một vùng nhớ được hệ điều hành cấp phát
cho chương trình C++ nhằm để lưu trữ giá trị vào bên trong vùng nhớ đó.
Ví dụ: int m; // một vùng nhớ có kích thước 4 bytes sẽ được cấp phát.Virtual memory & Physical memory
❖ Virtual memory & Physical
memory
▪ Chúng ta chỉ có thể trỏ đến
vùng nhớ ảo (virtual
memory) trên máy tính, còn
việc truy xuất đến bộ nhớ vật
lý (physical memory) từ bộ
nhớ ảo phải được thực hiện
bởi thiết bị phần cứng có tên
là Memory management unit
(MMU) và một chương trình
định vị địa chỉ bộ nhớ gọi
là Virtual address space .
90 trang |
Chia sẻ: thanhle95 | Lượt xem: 540 | Lượt tải: 1
Bạn đang xem trước 20 trang tài liệu Bài giảng Kỹ thuật lập trình - Chương 3: Con trỏ (Pointer) - Trịnh Tấn Đạt, để xem tài liệu hoàn chỉnh bạn click vào nút DOWNLOAD ở trên
Con Trỏ
(Pointer)
Trịnh Tấn Đạt
Khoa CNTT - Đại Học Sài Gòn
Email: trinhtandat@sgu.edu.vn
Website: https://sites.google.com/site/ttdat88/
Nội dung
▪ Biến tĩnh vs. Biến động
▪ Con trỏ
▪ Các phép toán trên con trỏ
▪ Con trỏ và mảng một chiều
▪ Cấp phát vùng nhớ động
▪ Con trỏ cấp 2
▪ Con trỏ và mảng nhiều chiều
▪ Mảng con trỏ
▪ Con trỏ hằng, void
▪ Con trỏ hàm (option)
Biến tĩnh vs. Biến động
▪ Một số hạn chế có thể gặp phải khi sử dụng các biến tĩnh:
o Cấp phát ô nhớ dư, gây ra lãng phí ô nhớ.
o Cấp phát ô nhớ thiếu, chương trình thực thi bị lỗi.
▪ Biến động:
o Khi chạy chương trình, kích thước của biến, vùng nhớ và địa chỉ vùng nhớ được cấp phát cho
biến có thể thay đổi.
o Sau khi sử dụng xong có thể giải phóng để tiết kiệm chỗ trong bộ nhớ.
▪ Variable: Biến là một ô nhớ đơn lẻ hoặc một vùng nhớ được hệ điều hành cấp phát
cho chương trình C++ nhằm để lưu trữ giá trị vào bên trong vùng nhớ đó.
Ví dụ: int m; // một vùng nhớ có kích thước 4 bytes sẽ được cấp phát.
Virtual memory & Physical memory
❖ Virtual memory & Physical
memory
▪ Chúng ta chỉ có thể trỏ đến
vùng nhớ ảo (virtual
memory) trên máy tính, còn
việc truy xuất đến bộ nhớ vật
lý (physical memory) từ bộ
nhớ ảo phải được thực hiện
bởi thiết bị phần cứng có tên
là Memory management unit
(MMU) và một chương trình
định vị địa chỉ bộ nhớ gọi
là Virtual address space .
Variable address & address-of operator
▪ Địa chỉ của biến (variable address) mà chúng ta nhìn thấy thật ra chỉ là những giá
trị đã được đánh số thứ tự đặt trên Virtual memory.
▪ Để lấy được địa chỉ ảo của biến trong chương trình, chúng ta sử dụng toán tử lấy
địa chỉ (address-of operator) ‘&’ đặt trước tên biến.
Ví dụ:
int x = 5;
std::cout << x << '\n'; // print the value of variable x
std::cout << &x << '\n'; // print the memory address of variable x
Reference
▪ Tham chiếu (Reference) : Mục đích của tham chiếu trong C++ là tạo ra một biến
khác có cùng kiểu dữ liệu nhưng sử dụng chung vùng nhớ với biến được tham
chiếu đến.
▪ Ví dụ:
Như vậy, mọi hành vi thay đổi giá trị của i_ref đều tác động trực tiếp đến i1.
Lưu ý: Biến tham chiếu sẽ có địa chỉ cố định sau khi khởi tạo. Chúng ta không thể tham chiếu lại lần nữa.
Lưu ý: address-of operator và
reference dùng chung 1 ký hiệu
&
Dereference operator
▪ Toán tử trỏ đến (dereference operator) hay còn gọi là indirection operator (toán tử
điều hành gián tiếp) được kí hiệu bằng dấu sao "*" cho phép chúng ta lấy ra giá trị
của vùng nhớ có địa chỉ cụ thể.
▪ Ví dụ:
Dereference operator
▪ Ngoài việc truy xuất giá trị trong vùng nhớ của một địa chỉ cụ thể, toán tử trỏ đến
(dereference operator) còn có thể dùng để thay đổi giá trị bên trong vùng nhớ đó.
Dereference operator
▪ Toán tử trỏ đến cho phép chúng ta thao tác trực tiếp trên Virtual memory mà không
cần thông qua định danh (tên biến).
▪ Khác với tham chiếu (reference), toán tử trỏ đến (dereference operator) không tạo
ra một tên biến khác, mà nó truy xuất trực tiếp đến vùng nhớ có địa chỉ cụ thể
trên Virtual memory.
Con trỏ (Pointer)
▪ Một con trỏ (pointer) là một biến (kiểu dữ liệu) được dùng để lưu trữ địa chỉ của
biến khác.
▪ Khác với tham chiếu, con trỏ là một biến có địa chỉ độc lập so với vùng nhớ mà
nó trỏ đến, nhưng giá trị bên trong vùng nhớ của con trỏ chính là địa chỉ của
biến (hoặc địa chỉ ảo) mà nó trỏ tới.
❖ Kiểu con trỏ cho phép:
o Truyền tham số kiểu địa chỉ
o Biểu diễn các kiểu, cấu trúc dữ liệu động
o Lưu trữ dữ liệu trong vùng nhớ heap
Con trỏ (Pointer)
▪ Khai báo con trỏ trong C/C++: Kiểu con trỏ phải được định nghĩa trên một kiểu cơ
sở đã được định nghĩa trước đó
▪ Ví dụ:
int x; // x là kiểu int
int *px; // px là con trỏ đến int
int *p1, *p2; // // p1 và p2 là con trỏ đến int
float *pf; // pf là con trỏ đến float
double *pd; // pd là con trỏ đến double
char c, d, *pc; // c và d kiểu char ; pc là con trỏ đến char
kiểucơsở *Tênkiểu;
Lưu ý: Dấu sao “*” trong con trỏ không phải là toán tử trỏ đến (dereference
operator) , nó là cú pháp được ngôn ngữ C/C++ qui định
Con trỏ (Pointer)
▪ Lưu ý : Kiểu dữ liệu của con trỏ không mô tả giá trị địa chỉ được lưu trữ bên
trong con trỏ, mà kiểu dữ liệu của con trỏ dùng để xác định kiểu dữ liệu của biến
mà nó trỏ đến trên bộ nhớ ảo.
Con trỏ (Pointer)
❖ Con trỏ và toán tử lấy địa chỉ & (address-of operator)
▪ “&”: toán tử lấy địa chỉ của 1 biến
▪ Địa chỉ của tất cả các biến trong chương trình đều đã được chỉ định từ khi khai báo
▪ Gán giá trị cho con trỏ: Giá trị mà biến con trỏ lưu trữ là địa chỉ của biến khác có
cùng kiểu dữ liệu với biến con trỏ.
Do đó, chúng ta cần sử dụng address-of operator để lấy ra địa chỉ ảo của biến rồi mới
gán cho con trỏ được. Lúc này, biến ptr sẽ lưu trữ địa chỉ ảo của biến value.
Con trỏ (Pointer)
▪ Ví dụ:
Con trỏ (Pointer)
▪ Ví dụ:
Lý do mà chúng ta gán được địa chỉ của biến value cho con trỏ
kiểu int (int *) là vì address-of operator của một biến
kiểu int trả về giá trị kiểu con trỏ kiểu int (int *).
Con trỏ (Pointer)
❖ Con trỏ và toán tử trỏ đến *
(dereference operator)
▪ “*”: toán tử truy xuất giá trị của
vùng nhớ được quản lý bởi con trỏ.
#include
#include
using namespace std;
char g = 'z';
int main()
{
char c = 'a';
char *p;
p = &c;
printf("%c\n", *p);
// hoac dung cout
cout<< *p <<endl;
p = &g;
cout<< *p <<endl;
return 0;
}
Con trỏ (Pointer)
❖ Con trỏ và toán tử gán “=”
▪ Khi có hai con trỏ cùng kiểu thì chúng ta có thể gán trực tiếp mà không cần sử
dụng address-of operator.
Con trỏ (Pointer)
▪ Ví dụ : Có sự khác biệt rất quan trọng khi thực hiện các phép gán:
int i = 10, j = 14;
int* p = &i;
int *q = &j;
*p = *q;
int i = 10, j = 14;
int *p = &i;
int *q = &j;
p = q;
Lưu ý: Khác với tham chiếu (reference), một con
trỏ có thể trỏ đến địa chỉ khác trong bộ nhớ ảo
sau khi đã được gán giá trị.
Tham chiếu (reference) không thể thay đổi địa chỉ
sau lần tham chiếu đầu tiên.
Con trỏ (Pointer)
▪ Ví dụ :
Con trỏ (Pointer)
❖ Con trỏ chưa được gán địa chỉ
▪ Con trỏ trong ngôn ngữ C/C++ vốn không an toàn. Nếu sử dụng con trỏ không hợp
lý có thể gây crash chương trình.
▪ Biến con trỏ có thể không cần khởi tạo giá trị ngay khi khai báo. Nhưng thực hiện
truy xuất giá trị của con trỏ bằng dereference operator khi chưa gán địa chỉ cụ thể
cho con trỏ, chương trình có thể bị đóng bởi hệ điều hành
Con trỏ (Pointer)
▪ Do đó, khi khai báo con trỏ nhưng chưa có địa chỉ khởi tạo cụ thể, chúng ta nên gán
cho con trỏ giá trị NULL.( giá trị NULL trong thư viên )
▪ Giá trị đặc biệt để chỉ rằng con trỏ không quản lý vùng nào. Giá trị này thường
được dùng để chỉ một con trỏ không hợp lệ.
#include
#include
using namespace std;
int main()
{
int *p = NULL;
if (p == NULL)
cout <<"con tro khong hop le" << endl;
else
cout << *p;
return 0;
}
Con trỏ (Pointer)
▪ Câu hỏi : Kiểm tra kết quả dòng lệnh sau
#include
using namespace std;
int main()
{
int a = 10;
int *p = &a;
*p = 5;
cout << a << endl;
cout << *p << endl;
return 0;
}
Con trỏ (Pointer)
▪ Câu hỏi: cho biết kết quả chương trình sau
#include
using namespace std;
int main()
{
int i = 10, j = 14, k;
int *p = &i;
int *q = &j;
*p += 1;
p = &k;
*p = *q;
p = q;
*p = *q;
cout << "i = " << i << ":" << &i <<endl;
cout << "j = " << j << ":" << &j <<endl;
cout << "k = " << k << ":" << &k <<endl;
cout << "*p = " << *p << ":" << p <<endl;
cout << "*q = " << *q << ":" << q <<endl;
return 0;
}
Biến Giá trị
i
j
k
p
q
Con trỏ (Pointer)
▪ Câu hỏi: cho biết kết quả sau
int n = 5;
int *p;
p = &n ;
cout << n <<endl;
cout << &n <<endl;
cout << p <<endl;
cout << *p <<endl;
cout << *(&n) << endl;
cout << &p <<endl;
Con trỏ (Pointer)
❖ Con trỏ - Truyền tham số địa chỉ (cho hàm)
#include
using namespace std;
void change_2(int &); // dung tham chieu
void change_1(int *); // con tro truyen tham so dia chi
int main()
{
int var1 = 5;
int var2 = 5;
change_1(&var1); // dung con tro truyen tham so dia chi
cout << "main: change_1: var = " << var1 <<endl;
change_2(var2); // dung tham chieu
cout << "main: change_1: var = " << var2 <<endl;
return 0;
}
void change_1(int *v)
{
(*v) += 5;
cout<< "change 1: var = "<< (*v) << endl;
}
void change_2(int &v)
{
v += 5;
cout<< "change 2: var = "<< v << endl;
}
Con trỏ (Pointer)
❖ Con trỏ và toán tử “+, - ” với số nguyên
▪ Ta có thể cộng (+), trừ (-) con trỏ với 1 số
nguyên N nào đó; kết quả trả về là 1 con
trỏ.
❑ Lưu ý :
▪ Con trỏ p có kiểu int, nên khoảng cách
giữa p và p + 1 là 4 bytes
❑ Lưu ý:
▪ Ta không thể cộng 2 con trỏ với nhau;
▪ Phép trừ 2 con trỏ cùng kiểu sẽ trả về 1
giá trị nguyên (int). Đây chính là khoảng
cách (số phần tử) giữa 2 con trỏ đó.
#include
using namespace std;
int main()
{
int var = 5;
int *p = &var;
int *q = p+2;
cout <<"p = " << p << endl;
cout <<"p+1 = " << p+1 << endl;
cout <<"q = " << q << endl;
cout <<"p+2 = " << p+2 << endl;
cout <<"p+3 = " << p+3 << endl;
cout <<"q - p = " << q-p << endl;
return 0;
}
Con trỏ và Mảng 1 chiều
▪ Biến kiểu mảng là địa chỉ tĩnh của một vùng nhớ, được xác định khi khai báo,
không thay đổi trong suốt chu kỳ sống.
▪ Biến con trỏ là địa chỉ động của một vùng nhớ, được xác định qua phép gán địa chỉ
khi chương trình thực thi.
Con trỏ và Mảng 1 chiều
▪ Địa chỉ của
mảng một chiều
và các phần tử
trong mảng một
chiều
Ví dụ:
int arr[] = { 32, 13, 66, 11, 22 };
//show address of arr in virtual memory
cout << &arr << endl;
//show address of the first element of arr
cout << &arr[0] << endl;
cout << arr << endl;
// address
cout << arr << endl;
cout << arr + 1 << endl;
cout << arr + 2 << endl;
//value
cout << *(arr) << endl;
cout << *(arr + 1) << endl;
cout << *(arr + 2) << endl;
▪ Các cách viết sau đây
là tương đương
o arr &arr[0] &arr
o arr+i &arr[i]
o *(arr+i) arr[i]
Con trỏ và Mảng 1 chiều
▪ Con trỏ trỏ đến mảng một chiều
Ví dụ:
int arr[] = { 3, 5, 65, 23, 11 };
int *ptr = &arr[2]; //ptr point to the 3rd element
cout << *ptr << endl;
cout << *(ptr - 1) << endl; //access the second element of arr
cout << *(ptr + 2) << endl; //access the last element of arr
// sai : cout << *(arr-1) ; không dùng phép toán “-” cho biến kiểu mảng
Con trỏ và Mảng 1 chiều
▪ Con trỏ trỏ đến mảng một chiều
int arr[] = { 3, 5, 65, 23, 11 };
int *ptr = arr; //ptr point to &arr[0] // arr = ptr : SAI
for (int i = 0; i < 5; i++)
{
cout << *(ptr + i) << " ";
cout << *(arr + i) << " ";
cout << ptr[i] << " ";
cout << arr[i] << " ";
}
Cấp phát bộ nhớ động
❖Vùng nhớ heap và vùng nhớ stack
▪ Vùng nhớ stack:
o Khai báo các biến, mảng hay các tham số trong
hàm, kể cả hàm main (gọi là biến cục bộ) ->
được lưu trong vùng nhớ stack.
o Khi kết thúc hàm, vùng nhớ của biến này sẽ tự
động bị thu hồi.
o Kích thước vùng nhớ stack khá hạn chế, giả sử
bạn muốn khai báo một mảng có kích thước 1
triệu phần tử hay lớn hơn, chương trình có thể
sẽ bị crash tuỳ theo dung lượng RAM mà bạn
có.
char ch_array_1[1024 * 1000];
char ch_array_2[1024 * 10000]; // tràn vùng nhớ Stack báo lỗi
Cấp phát bộ nhớ động
❖ Vùng nhớ heap và vùng nhớ stack
▪ Vùng nhớ heap:
o Các biến, mảng được khai báo bằng kỹ thuật cấp phát động được lưu trong vùng
nhớ heap.
o Sau khi kết thúc hàm, vùng nhớ của các biến này không bị thu hồi. Và người lập
trình phải có nhiệm vụ thu hồi vùng nhớ của biến khi không còn sử dụng. Nếu quên
sẽ gây ra hiện tượng “rò rỉ” bộ nhớ (memory-leaking).
o Kích thước vùng nhớ heap tương đối thoải mái, có thể khai báo được mảng mới kích
thước lớn. (tuy nhiên vẫn phụ thuộc vào dung lượng của RAM)
Cấp phát bộ nhớ động
❖ Cấp phát bộ nhớ tĩnh vs. cấp phát bộ nhớ động
▪ Cấp phát bộ nhớ tĩnh: được áp dụng cho biến static và biến toàn cục.
o Vùng nhớ của các biến này được cấp phát ngay khi chạy chương trình.
o Kích thước của vùng nhớ được cấp phát phải được cung cấp tại thời điểm biên dịch
chương trình.
o Đối với việc khai báo mảng một chiều, đây là lý do tại sao số lượng phần tử là hằng số.
Ví dụ : int i=10;
double Arr[1000]; // có thể dẫn đến dư thừa khi không dùng hết 1000 ô nhớ hoặc
không thể mổ rộng khi muốn dùng hơn 1000 phần tử
Cấp phát bộ nhớ động
❖ Cấp phát bộ nhớ tĩnh vs. cấp phát bộ nhớ động
▪ Cấp phát bộ nhớ động: được sử dụng để cấp phát vùng nhớ cho các biến cục bộ,
tham số của hàm.
o Bộ nhớ được cấp phát tại thời điểm chương trình đang chạy, khi chương trình đi vào một
khối lệnh.
o Các vùng nhớ được cấp phát sẽ được thu hồi khi chương trình đi ra khỏi một khối lệnh.
o Kích thước vùng cần cấp phát cũng phải được cung cấp rõ ràng.
Cấp phát bộ nhớ động
▪ Có thể chỉ định vùng mới cho 1 con trỏ quản lý bằng các lệnh hàm malloc, calloc (
của C) hoặc toán tử new (của C++)
▪ Vùng nhớ do lập trình viên chỉ định phải được giải phóng bằng hàm free (của C)
hoặc toán tử delete (của C++))
Ví dụ: int *i = new int;
double *Arr = new double[n]; // nhập vào giá trị n cụ thể
Cấp phát bộ nhớ động
▪ Hàm malloc (memory allocation) và calloc (contiguous allocation) cho phép cấp phát
các vùng nhớ ngay trong lúc chạy chương trình
Cấp phát bộ nhớ động
▪ Toán tử sizeof: nhận một tham số là bất kỳ kiểu dữ liệu nào và trả về kích cỡ của
kiểu dữ liệu đó.
▪ Ví dụ:
cout<< sizeof(char); // 1
cout<< sizeof(int); // 4
cout<< sizeof(float); // 4
cout<< sizeof(double); // 8
trả về kích cỡ của các kiểu dữ liệu trong C
trả về kích cỡ của các con trỏ tới các kiểu dữ liệu khác nhau
cout<< sizeof(char*); // 4
cout<< sizeof(int*); // 4
cout<< sizeof(float*); // 4
cout<< sizeof(double*); // 4
Cấp phát bộ nhớ động
▪ malloc : Hàm malloc() thực hiện cấp phát bộ nhớ bằng cách chỉ định số byte cần cấp
phát. Hàm này trả về con trỏ kiểu void cho phép chúng ta có thể ép kiểu về bất cứ kiểu
dữ liệu nào
void* malloc (size_t size);
Ví dụ:
int *a = (int *) malloc(sizeof( int )); // cấp phát 4 bytes
int *arr = (int *) malloc( 10 * sizeof( int )); // cấp phát 40 bytes
float *b = (float *) malloc(100*sizeof(float)); // cấp phát 400 bytes
Ép kiểu dữ liệu cho con trỏ
Cấp phát bộ nhớ động
▪ calloc (): hàm calloc() thực hiện cấp phát bộ nhớ và khởi tạo tất cả các ô nhớ có giá
trị bằng 0.
void* calloc (size_t num, size_t size);
Ví dụ:
int *b = (int *) calloc( 10, sizeof( int ));
int *a = (int *) calloc( 1, sizeof( int ));
char *s = (char *) calloc( 100, sizeof(char ));
Cấp phát bộ nhớ động
▪ Giải phóng vùng nhớ: dùng hàm free() trong C.
▪ Khi thoát khỏi hàm, các biến khai báo trong hàm sẽ “biến mất”. Tuy nhiên các vùng
nhớ được cấp phát động vẫn còn tồn tại và được “đánh dấu” là đang “được dùng”
→ bộ nhớ của máy tính sẽ hết.
▪ Cú pháp : free(ptr); // ptr là con trỏ
Cấp phát bộ nhớ động
▪ Ví dụ: dùng malloc() or calloc() và free() nhập vào một mảng n số nguyên , in ra tổng
các phần tử trong mảng
#include
// thu vien dung cap phat dong
#include
using namespace std;
int main()
{
int n, i, *ptr, sum = 0;
cout<<"Nhap so luong phan tu:" ;
cin >> n;
ptr = (int *)malloc(n * sizeof(int)); // cap phat dong
// ptr = (int *)calloc(n, sizeof(int)); // dung calloc
// check if cap phat ko thanh cong
if (ptr == NULL)
{
printf("Co loi! khong the cap phat bo nho.");
return 0;
}
// .
//
cout << "Nhap cac gia tri: ";
for (i = 0; i < n; ++i)
{
cin >> *(ptr + i); // hoac dung cin >> ptr[i];
sum += *(ptr + i); // hoac dung sum += ptr[i];
}
cout<<"Tong = " << sum ;
// thu hoi vung nho
free(ptr);
return 0;
}
Cấp phát bộ nhớ động
▪ Mở rộng vùng nhớ dùng realloc()
▪ Nếu việc cấp phát bộ nhớ động không
đủ hoặc cần nhiều hơn mức đã cấp
phát, bạn có thể thay đổi kích thước
của bộ nhớ đã được cấp phát trước đó
bằng cách sử dụng hàm.
▪ Cú pháp :
void *realloc(void *block, size_t size);
#include
#include
using namespace std;
int main()
{
int *ptr, i , n1, n2;
cin >> n1; // nhap n1
ptr = (int*) malloc(n1 * sizeof(int));
cout << "Dia chi cua vung nho vua cap phat:" << ptr;
cout << "\nNhap lai so luong phan tu: " ;
cin >> n2;
// cap nhat lai vung nho
ptr = (int*) realloc(ptr, n2 * sizeof(int));
cout << "Dia chi cua vung nho vua cap phat:" << ptr;
// thu hoi vung nho
free(ptr);
return 0;
}
Cấp phát bộ nhớ động
▪ Cấp phát động trong C++
o Cấp phát bộ nhớ động dùng toán tử new (Toán tử new được dùng để xin cấp phát vùng nhớ trên
phân vùng Heap của bộ nhớ ảo)
Cú pháp : một vùng nhớ
*tencontro = new ;
dãy vùng nhớ liên tục nhau (mảng một chiều)
*tencontro = new [num_of _elements];
Ví dụ:
int *ptr = new int; // dynamically allocate an integer
double *p_double = new double;
int *p_arr = new int[10]; // cho mảng 10 phần tử
char *c_str = new char [100]; // cho mảng 100 phần tử
❖ Điều khiến cho kỹ thuật Dynamic memory allocation khác với Static memory allocation là số lượng
phần tử có thể được cung cấp trong khi chương trình đang chạy. Ví dụ:
int num_of_elements;
cout << "Enter number of elements you want to create: ";
cin >> num_of_elements;
int *p_arr = new int[num_of_elements];
Cấp phát bộ nhớ động
▪ Cấp phát động trong C++
o Giải phóng bộ nhớ dùng toán tử delete
• Khi không muốn sử dụng tiếp vùng nhớ đã được cấp phát cho chương trình
trên Heap, chúng ta nên trả lại vùng nhớ đó cho hệ điều hành.
• Thật ra khi chương trình kết thúc, tất cả vùng nhớ của chương trình đều bị hệ điều
hành thu hồi, nhưng chúng ta nên giải phóng vùng nhớ không cần thiết càng sớm
càng tốt.
• Để xóa một vùng nhớ, chúng ta cần có một địa chỉ cụ thể, địa chỉ đó được giữ bởi
con trỏ sau khi gán địa chỉ cấp phát cho nó:
• Đối với dãy vùng nhớ liên tục được cấp phát trên Heap, chúng ta cần thêm vào toán
tử [ ] để báo với hệ điều hành rằng vùng nhớ đã được cấp phát không dùng cho một
biến đơn.
int *p = new int;
delete p;
int *p_arr = new int[10];
delete[] p_arr;
Cấp phát bộ nhớ động
▪ Lưu ý:
Cấp phát bộ nhớ tĩnh Cấp phát bộ nhớ động
int Arr [100]; // khai báo mảng Arr
Nhập vào n và các phân tử của mảng
-Không thề nhập n > 100
-Nếu n dư thừa tài nguyên
int Arr[n]; // Wrong
Nhập vào n và các phân tử của mảng
int *Arr = new int[n];
- Hạn chế sự lãng phí tài nguyên
- Có thể mở rộng vùng nhớ khi thực thi chương
trình.
Cấp phát bộ nhớ động
▪ Tóm tắt
Trong C Trong C++
• Cấp phát động
- Hàm malloc
int *a = (int *) malloc( N* sizeof( int ));
-Hàm calloc (gán giá trị cho tất cả các phần tử của
vùng nhớ vừa cấp phát = 0)
int *ptr= (int *) calloc(N, sizeof(int));
- Hàm realloc (để thay đổi kích thước bộ nhớ đã
cấp phát với ptr là địa chỉ của bộ nhớ đã cấp phát)
ptr = (int *) realloc (ptr, M* sizeof(int));
// ex : M > N
•Thu hồi vùng nhớ
-Hàm free
free (ptr);
• Cấp phát động
-Toán tử new
int *p_int = new int;
double *Arr = new double[N];
•Thu hồi vùng nhớ
-Toán tử delete
delete p_int;
delete [] Arr; // thêm toán tử []
để báo với hệ điều hành rằng vùng
nhớ đã được cấp phát không dùng
cho một biến đơn.
Con trỏ và cấp phát động
▪ Lưu ý: có thể dùng con trỏ và cấp phát bộ nhớ động để biểu diễn cho mảng
▪ Làm việc trên con trỏ và cấp phát bộ nhớ động phải tuân thủ các qui tắc sau:
1. Khai báo con trỏ int *p;
2. Cấp phát bộ nhớ động cho con trỏ p = new int[10];
3. Kiểm tra xem cấp phát v