SỬ DỤNG HÀM TRONG C
CHƯƠNG 6 KIỂU DỮ LIỆU CON TRỎ
6.8 CẤP PHÁT BỘ NHỚ
Cho đến thời điểm này thì chúng ta đã biết rằng tên của một mảng thật ra là một con trỏ trỏ tới phần tử đầu tiên của mảng. Hơn nữa, ngoài cách định nghĩa một mảng thông thường có thể định nghĩa một mảng như là một biến con trỏ. Tuy nhiên, nếu một mảng được khai báo một cách bình thường, kết quả là một khối bộ nhớ cố định được dành sẵn tại thời điểm bắt đầu thực thi chương trình, trong khi điều này không xảy ra nếu mảng được khai báo như là một biến con trỏ. Sử dụng một biến con trỏ để biểu diễn một mảng đòi hỏi việc gán một vài ô nhớ khởi tạo trước khi các phần tử mảng được xử lý. Sự cấp phát bộ nhớ như vậy thông thường được thực hiện bằng cách sử dụng hàm thư viện malloc().
Một mảng số nguyên một chiều A có 50 phần tử có thể được khai
báo như sau:
int *A;
thay vì:
int A[50];
Điểm khác biệt cơ bản của hai câu lệnh khai báo trên như sau: Đối với câu
lệnh thứ nhất,
mảng A được khai báo như một con trỏ kiểu nguyên và nó chưa được gán bộ nhớ,
khi nào cần
dùng đến, người lập trình sẽ gọi lệnh cấp phát bộ nhớ cho nó. Vì vậy, khai báo
này thường
được gọi là khai báo động. Trong khi đó, với câu lệnh khai báo thứ 2, một khối ô
nhớ đủ để
chứa 10 số nguyên sẽ được dành sẵn cho A, cách khai báo này được gọi là khai báo tĩnh.
121
Một chương trình C có thể lưu trữ các thông tin trong bộ nhớ của máy tính
theo hai cách
chính. Phương pháp thứ nhất bao gồm các biến toàn cục và cục bộ - bao gồm các
mảng. Trong
trường hợp các biến toàn cục và biến tĩnh, sự lưu trữ là cố định suốt thời gian thực
thi chương
trình. Các biến này đòi hỏi người lập trình phải biết trước tổng số dung lượng bộ
nhớ cần thiết
cho mỗi trường hợp. Phương pháp thứ hai, thông tin có thể được lưu trữ thông
qua hệ thống
cấp phát động của C. Trong phương pháp này, sự lưu trữ thông tin được cấp phát
từ vùng nhớ
còn tự do và khi cần thiết.
6.8.1 Hàm malloc()
Hàm malloc()là một trong các hàm thường được dùng nhất, nó cho phép thực hiện việc cấp phát bộ nhớ từ vùng nhớ còn tự do. Cú pháp của hàm như sau:
void * malloc ( size_t size );
Tham số size của hàm malloc() là một số nguyên xác định số byte cần cấp phát, thường được tính bằng tổng số phần tử nhân với kích thước một phần tử.
malloc() trả về một con trỏ không kiểu (con trỏ void) trỏ đến vùng nhớ được cấp phát hoặc NULL nếu không đủ bộ nhớ để cấp phát. Để trả về một con trỏ đến một kiểu dữ liệu cụ thể cần thực hiện thao tác ép kiểu trên giá trị trả về của malloc().
Cấp phát bộ nhớ cho mảng mộtchiều
Trong trường hợp mảng một chiều Ađược khai báo như một con trỏ, khối lượng bộ nhớ có thể được gán như sau:
A = malloc(50 *sizeof(int));
Hàm sizeof() thường được sử dụng để xác định kích thước của một kiểu dữ liệu. Việc sử dụng hàm sizeof() sẽ tạo khả năng uyển chuyển cho mã lệnh.Nếu máy tính sử dụng 4 byte để lưu chữ một số nguyên (sizeof(int)= 4) thì lệnh khai báo trên sẽ dành một khối bộ nhớ có kích thước 504 = 200 byteđủ để lưu trữ 50 giá trị kiểu nguyên.
Ví dụ 6.18 sau đây khai báo, cấp phát bộ nhớ động và nhập giá trị cho một
mảng một chiều
có n phần tử (n là một giá trị bất kỳ được nhập từ bàn phím) sau đó sắp xếp mảng
theo chiều
tăng dần và hiển thị mảng đã sắp ra màn hình. Hàm swap() được sử dụng để hoán
đổi vị trí các
phần tử trong mảng.
Ví dụ 6.18:
#include
<stdio.h>
#include
<malloc.h
>
void InputArray(int
*p, int n){
for (int i=0;
i<n; i++){
printf("A[%d] = ",i); scanf("%d",p+i);
} }
void DisplayArray(int *p, int n){
for (int i=0; i<n; i++){
printf("%d ", *(p+i));
}printf("\n");
}
int Swap (int
*x, int *y){
int temp =
*x; *x = *y;
*y = temp;
122
}
void Sort(int
*p, int n){
int i,j;
for (i=0; i<n-1; i++) for (j=i+1;
j<n; j++) (p[i]>p[j]) if
Swap(p+i, p+j);
} i n t m a i n ( ) { i n t n
; printf("Enter n = ");
scanf("%d",&n); int *A = (int*)
malloc(n*sizeof(int)); if (A==NULL){
printf("Not enough the memory! ");
exit(0);
}
InputArray(A,n);
printf("Before sort: ");
DisplayArray(A ,n);
printf("After sort: ");
Sort(A, n);
DisplayA rray(A,n );free(A);
}
Kết quả thực hiện của chương trinh như sau:
Enter n = 7 A[0] = 3 A[1] = 5
A[2] = 1
A[3] = 8
A[4] = 5 A[5] = 9
A[6] = 4
Before sort: 3 5 1 8 5 9 4 After sort: 1 3 4 5 5 8 9
Chú ý: Đi kèm với mỗi thao tác cấp phát bộ nhớ động là thao tác giải phóng (hủy) vùng nhớ đã được cấp phát cho các biến động khi không cần dùng đến chúng nữa. Thao tác giải phóng bộ nhớ được thực hiện bởi hàm free().
Cấp phát bộ nhớ cho mảng hai chiều
Trong trường hợp mảng haichiều Ary (5 hàng 10 cột) được khai báo như một con trỏ đến một nhóm các mảng một chiều. Sự khai báo và cấp phát bộ nhớ cho Ary sẽ như sau:
int(*Ary)[10];
Ary = (int(*)[10])malloc(5 *10*sizeof(int));
Do hàm malloc() trả về một con trỏ trỏ đến kiểu rỗng, mà Ary lại là một con
trỏ trỏ đến
một nhóm các mảng một chiều nên sự chuyển đổi kiểu là cần thiết. Trong câu
lệnh trên
(int(*)[10])sẽ đổi kiểu trả về của malloc() thành kiểu con trỏ trỏ đến một
nhóm các
mảng mộtchiều.
123
Chú ý rằng nếu một sự khai báo của mảng phải chứa phép gán các giá trị khởi tạo thì mảng phải được khai báo theo cách khai báo thông thường (cấp phát bộ nhớ tĩnh), không thể dùng biến con trỏ, chẳng hạn:
Đối với mảng 1 chiều:
int A[] = {10,11,12,13,14,15}
hoặc
A[6] =
{10,11,12,13,14,15}
Đối với mảng 2 chiều:
int Ary[][3] = {{1,2,3},{4,5,6}}
hoặc
int Ary[][3] = {{1,2,3},{4,5,6}}
Chương trình trong ví dụ 6.19 sau đây sẽ khai báo và cấp phát bộ nhớ động để lưu m xâu kýtự được nhập từ bàn phím (mỗi xâu kýtự có tối đa 50 kýtự), sau đó sắp xếp các chuỗi kýtự theo thứ tự alphabet và hiển thị ra màn hình.
Ví dụ 6.19:
#include
<stdio.h>
#include
<malloc.h
>
#include
<string.h
>
void InputString(char (*p) [50], int m){
for (int i=0; i<m; i++){
printf("Input string
%d: ", i);
gets(p[i]);
} }
void DisplayString(char (*p)
[50], int m){
for( int i = 0; i < m; i++ ) {
puts(p[i]);
} }
void SortString(char (*p) [50], int m){
int i,j;
char tmp[50];
for (i = 0; i < m-1; i++) for (j=i+1; j<m; j++) if (strcmp(p[i],p[
j])>0)
{ strcpy(tmp, p[i]);
strcpy(p[
i], p[j]);
strcpy(p[j ], tmp);
} } i n t m a i n ( ) {
int m;
char (*str_ary)[50];
/*
Khai báo*/ printf("Input m = ");
scanf("%d",&m);
fflush(stdin);
str_ary = (char(*)
[50])malloc(m*50*sizeof(char));
InputString(str_ary, m); /*Nhập các xâu kí tự*/ printf("Before sort:\n ");
124
DisplayString(str_ary , m); printf("After
sort:\n ");
SortString(str_ary, m);
DisplayString(str_ary , m); Free(str_ary);
}
/*Hiển thị xâu chưa sắp*/
/*Sắp xếp các xâu kí tự*/ /*Hiển thị xâu sau khi sắp*/ /*Giải phóng bộ nhớ*/
Kết quả thực hiện của chương trình như sau:
Input m = 5
Input string 0:
Nguyen Thi Lan
Input string 1:
Nguyen Thi Mai
Input string 2:
Nguyen Van Nam
Input string 3: Le
Hung Cuong
Input string 4: Ngo
Quang Sang
Before sort:
Nguyen Thi Lan Nguyen Thi Mai Nguyen Van Nam Le Hung Cuong Ngo Quang Sang After sort:
Le Hung Cuon g Ngo Quan g Sang Nguy en Thi Lan Nguy en Thi Mai Nguy en
Van Nam
Từ ví dụ 6.19 cho thấy khi một mảng hai chiều được khai báo dưới dạng một con trỏ trỏ đến một nhóm các mảng mộtchiều thì chúng ta chỉ có thể tùy chọn số hàng còn số cột (số phần tử trên một hàng) luôn phải xác định trước.
Trong trường hợp muốn cấp phát động hoàn toàn, ta phải khai báo mảng dưới
dạng con trỏ
đa cấp. Chẳng hạn, để cấp phát bộ nhớ động cho một mảng haichiều kiểu int, kích
thước mn
(m, n là các giá trị bất kỳ được nhập từ bàn phím). Khi đó, việc khai báo và cấp
phát mảng sẽ
như sau:
int**Ary;
Ary =
(int**)malloc(m*sizeof(int
*)) for (int i=0; i<m; i+
+) Ary[i] = (int*) malloc(n*sizeof(int));
Chương trình trong ví dụ 6.20 sau đây sẽ khai báo và cấp phát bộ nhớ động cho một mảng hai chiều kiểu số thực (float), nhập các giá trị cho mảng từ bàn phím, sau đó tìm giá trị nhỏ nhất trong mảng và hiển thị các giá trị đã nhập ra màn hình.
Ví dụ 6.20:
#include
<stdio.h>
#include
<malloc.h
>
void InputArray(float**p, int m, int n){
int i,j;
for (i=0; i<m; i++) for (j=0; j<n; j++){
printf("Array[%d][%d] = ", i, j);
125
scanf("%f",*(p+i)+j);
} }
void DisplayArray(float**p, int m, int n){
int i,j;
for (i=0;
i<m; i++){
for (j=0;
j<n; j++)
printf("%4.2f ",
*(*(p+i)+j));
printf("\n");
} }
float FindMin(float**p, int m, int n){
int i, j;
float min = *(*p);
for (i=0; i<m; i++) for (j=0; j<n; j++)
if( p[i][j] < min) min = p[i][j];
return min;
}int main(){
int i, m,n;
float
**Ary;
printf("Input number of rows: "); scanf("%d", &m);
printf("Input number of columns: "); scanf("%d", &n);
Ary = (float**)
malloc(m*sizeof(float*)); for (i=0; i<m; i++)
Ary[i] = (float*)
malloc(n*sizeof(float));
InputArray(Ary,m,n);
DisplayArray(Ary,m,n);
printf("The min value is:
%4.2f",FindMin(Ary,m,n)); free(Ary);
}
Kết quả thực hiện của chương trình như sau:
Input number of rows:2
Input number of columns:3 A[0][0]
= 2.11
A[0][1]
= 5.12
A[0][2]
= 1.13
A[1][0]
= 4.15
A[1][1]
= 6.12
A[1][2]
= 7.16
2.11 5.12 1.13 4.15 6.12 7.16
The min value is: 1.13
Sau các lệnh cấp phát bộ nhớ trong đoạn mã trên:
Ary = (float**)
malloc(m*sizeof(float*)); for (i=0; i<m; i++)
Ary[i] = (float*) malloc(n*sizeof(float));
126
Một khối bộ nhớ kích thước mn sẽ được cấp phát và con trỏ Ary sẽ trỏ đến địa
chỉ ô nhớ đầu
tiên của vùng nhớ được cấp phát. Lúc này Ary thực chất là một mảng hai chiều.
Trong ba lời gọi
hàm InputArray(Ary,m,n), DisplayArray(Ary,m,n) và FindMin(Ary,m,n), giá trị của
con trỏ p
tương ứng trong phần đối số của các hàm đã được gán bằng tên mảng (địa chỉ phần
tử đầu tiên của
mảng) Ary. Như vậy, để truy xuất đến giá trị của phần tử hàng i, cột j có thể sử dụng câu lệnh
*(*(p+i)+j) hoặc đơn giản là p[i][j].
6.8.2 Hàm free()
Hàm này có thể được sử dụng để giải phóng bộ nhớ khi nó không còn cần thiết. Dạng tổng quát của hàm free():
void free (void *ptr);
Hàm free() giải phóng không gian được trỏ bởi ptr, không gian được giải phóng này có thể sử dụng trong tương lai. ptr đã sử dụng trước đó bằng cách gọi đến malloc(), calloc(), hoặc realloc(), calloc() và realloc() (sẽ được thảo luận sau). Cách thức sử dụng hàm free() đã được thể hiện cụ thể trong ví dụ 6.13 và 6.14 ở trên.
6.8.3 Hàm calloc()
calloc() tương tự như malloc(), nhưng khác biệt chính là mặc nhiên các giá trị
được lưu
trong không gian bộ nhớ đã cấp phát là 0. Với malloc(), cấp phát bộ nhớ có thể có
giá trị bất
kỳ.
calloc() đòi hỏi hai đối số. Đối số thứ nhất là số các biến mà bạn muốn cấp phát bộ nhớ cho. Đối số thứ hai là kích thước của mỗi biến.
void * calloc ( size_t num, size_t size)
Giống như malloc(), calloc() sẽ trả về một con trỏ rỗng (void) nếu sự cấp phát bộ nhớ là thành công, ngược lại nó sẽ trả về một con trỏ NULL.
Ví dụ 6.21 sau đây sử dụng hàm calloc()để cấp phát bộ nhớ cho một mảng một chiều các số nguyên, sau đó tìm kiếm những số nguyên tố trong mảng và hiển thị ra màn hình.
Ví dụ 6.21:
#include <stdio.h>
void InputArray(int
*ptr, int n) {
int i;
printf("\n\nInput array:\n"); for (i=0; i<n; i++){
printf("A[%d ] = ", i);
scanf("%d",ptr+i
); } }
void DisplayArray(int
*ptr, int n) {
for (int i=0; i<n; i++) printf("%d ", ptr[i]);
}
127
int CheckPrim(i nt x){
int i;
for (i=2;
i<=x/2; i++) if (x
%i==0)
return 0;
return 1;
}void DisplayPrim(int *ptr, int n){
printf("\nDisplay prim numbers:\n");
for (int i=0; i<n; i++) if (CheckPrim(ptr[i]))
printf("%d ", ptr[i]);
}int main(){
int *A, n;
printf("Inp ut n = ");
scanf("%d",
&n);
A = (int*) calloc(n, sizeof(int));
printf("\nDiplay initial
array: \n");
DisplayArray(A,n);
InputArray(A,n);
printf("\nDisplay entered
array: \n");
DisplayArray(A,n);
Display Prim(A, n);
free(A) } ;
Kết quả thực hiện của chương trình như sau:
Input n = 6
Diplay initial array:
0 0 0 0 0 0 Input array:
A[0] = 3 A[1] = 4
A[2] = 6
A[3] = 7
A[4] = 8
A[5] = 5
Entered array:
3 4 6 7 8 5
Display prim numbers:
3 7 5
Trong đoạn mã trên, hàm InputArray() được sử dụng để nhập giá trị cho
các phần tử
mảng, hàm DisplayArray() hiển thị giá trị các phần tử mảng ra màn
hình, hàm
CheckPrim()kiểm tra một số có phải là số nguyên tố hay không (kết quả trả về của
hàm là 1
nếu số cần kiểm tra là số nguyên tố), hàm DisplayPrim()hiển thị tất cả các số
nguyên tố trong
mảng. Cũng giống như việc cấp phát động bởi hàm malloc(), những vùng bộ nhớ
cấp phát bởi
hàm calloc(), sau khi không cần sử dụng nữa, sẽ được giải phóng bằng hàm free().
128
Các kết quả của việc gọi hàmDisplayArray(A,n)trước khi gọi hàm nhập mảng InputArray()cho thấy hàm calloc() đã cấp phát bộ nhớ và khởi tạo giá trị bằng 0 cho tất cả các phần tử mảng. Điều này cũng rất hữu dụng khi làm việc với mảng đa chiều.
6.8.4 Hàm realloc()
Giả sử chúng ta đã cấp phát một số byte cho một mảng nhưng sau đó lại muốn thêm các giá trị. Có thể làm điều này bằng cách cấp phát một mảng lớn hơn và sao chép mọi thứ vào một mảng lớn hơn đó nhưng cách này không hiệu quả. C cho phép cấp phát thêm các byte sử dụng bằng cách gọi hàm realloc(), mà dữ liệu của bạn không bị mất đi.
Cú pháp của hàm realloc()như sau:
void * realloc ( void *ptr, size_t size );
Trong đó, đối số thứ nhất ptr là một con trỏ tham chiếu đến bộ nhớ. Đối số thứ hai sizelà tổng số byte cần cấp phát thêm. realloc() trả về một con trỏ không kiểu (void) nếu thành công, ngược lại một con trỏ NULL được trả về. Trường hợp gọi hàm với giá trị đối số size =0 thì tương đương với việc gọi hàm free().
Chương trình trong ví dụ 6.22 sau đây trước tiên sẽ sử dụng calloc() để cấp phát đủ bộ nhớ cho một mảng kiểu số thực (float) có n phần tử. Sau đó realloc()được gọi để mở rộng mảng thêm m phần tử.
Ví dụ 6.22:
#include <stdio.h>
void InputArray(int *ptr, int start, int n){
int i;
printf("\nInput array:\n");
for (i=start;
i<start+n; i++){
printf("A[%d] =
", i);
scanf("%d",ptr+i);
} }
void DisplayArray(int *ptr, int n){
for (int i=0; i<n; i++) printf("%d ", ptr[i]);
}
int main(){
int *A, *ptr, n, m;
printf("Inp ut n = ");
scanf("%d",
&n);
A = (int*) calloc(n, sizeof(int));
InputArray(A, 0, n);
printf("Entered array: \n");
DisplayArray(A,n);
printf("\nEnter number of external items: "); scanf("%d",&m);
ptr = (int*) realloc(A,
(m+n)*sizeof(int)); if (A!=NULL) { InputArray(ptr, n, m);
printf("External array:\n");
DisplayArray(A, n+m);
free(ptr);
}
129
else{
printf("Not enough memory!");
free(A);
} }
Kết quả thực hiện của chương trình như sau:
Input n = 3
In pu t ar ra y:
A[
0]
= 1
A[
1]
= 2
A[
2]
= 3
Ente red arra y:
1 2 3
Enter number of external items: 4 Input array:
A [ 3 ]
= 4
A [ 4 ]
= 5
A [ 5 ]
= 6
A [ 6 ]
= 7
External array:
1 2 3 4 5 6 7
Trong đoạn mã trên, hàm InputArray()được sử dụng để nhập giá trị cho
các phần tử
mảng, hàm này nhận bađối số ptr (mảng cần nhập), start (chỉ số của phần tử bắt
đầu được
nhập), n (số phần tử cần nhập). Hàm Display() để hiển thị mảng. Đầu tiên, người
dùng chọn số
phần tử cần nhập n = 3, và nhập các giá trị 1, 2, 3 tương ứng cho 3 phần tử. Sau đó
mảng được
mở rộng thêm m = 4 phần tử và giá trị các phần tử này được nhập lần lượt là 4, 5,
6, 7. Lệnh
DisplayArray() cuối cùng sẽ hiển thị tất cả 7 giá trị đã được nhập ra màn hình.
130
CHƯƠNG 7