- 1 1. Giới thiệu
- 2 2. Mảng hai chiều là gì?
- 3 3. Khai báo và khởi tạo mảng hai chiều
- 3.1 Cách khai báo mảng hai chiều
- 3.2 Khởi tạo mảng hai chiều
- 3.3 Ví dụ mã: Khai báo và khởi tạo mảng
- 3.4 Kết quả xuất ra
- 3.5 4. Cách sử dụng mảng hai chiều: Truy cập và thao tác phần tử
- 3.6 Truy cập phần tử
- 3.7 Thao tác trên phần tử
- 3.8 Vòng lặp với mảng hai chiều
- 3.9 Ví dụ ứng dụng: Gán giá trị cố định cho toàn bộ phần tử
- 4 5. Cấu trúc bộ nhớ của mảng hai chiều
- 5 6. Ví dụ thực tế: Phép toán ma trận và tạo bàn cờ game
- 6 7. Quan hệ giữa mảng hai chiều và con trỏ
- 6.1 Quan hệ cơ bản giữa mảng hai chiều và con trỏ
- 6.2 Tham chiếu phần tử bằng con trỏ
- 6.3 Xử lý mảng hai chiều như con trỏ
- 6.4 Tạo mảng hai chiều động bằng con trỏ
- 6.5 8. Cách cấp phát và giải phóng mảng hai chiều động
- 6.6 Nguyên tắc cơ bản của cấp phát động
- 6.7 Hai cách tạo mảng hai chiều động
- 6.8 Lưu ý khi cấp phát động
- 7 9. Những lưu ý khi sử dụng mảng hai chiều
- 8 10. Tổng kết
1. Giới thiệu
Ngôn ngữ C là một trong những ngôn ngữ lập trình quan trọng và lâu đời nhất. Trong đó, “mảng” là chức năng cơ bản không thể thiếu để quản lý và thao tác dữ liệu một cách hiệu quả. Đặc biệt, “mảng hai chiều” rất hữu ích khi xử lý dữ liệu dạng ma trận hoặc bảng.
Bài viết này sẽ giải thích về mảng hai chiều trong C một cách dễ hiểu cho người mới bắt đầu. Nội dung sẽ được trình bày theo từng bước, từ cách sử dụng cơ bản đến các phương pháp ứng dụng nâng cao, giúp người đọc có thể áp dụng trực tiếp khi viết chương trình.
Tầm quan trọng của mảng trong C
Mảng trong C là cấu trúc dữ liệu cho phép quản lý nhiều giá trị cùng kiểu dữ liệu một cách tập trung. Điều này giúp tiết kiệm công sức khai báo từng biến riêng lẻ và giữ cho mã nguồn gọn gàng hơn. Ví dụ, khi cần lưu trữ nhiều số hoặc chuỗi ký tự, việc sử dụng mảng giúp thao tác dễ dàng và hiệu quả hơn.
Đặc biệt, “mảng hai chiều” có ích trong các trường hợp sau:
- Tính toán ma trận toán học
- Quản lý bàn cờ trong trò chơi (ví dụ: cờ vua, cờ Othello)
- Xử lý dữ liệu dạng bảng hoặc bảng tính
Những gì bạn sẽ học trong bài viết này
Trong bài viết này, bạn sẽ lần lượt tìm hiểu các nội dung sau:
- Cấu trúc cơ bản và cách khai báo mảng hai chiều
- Cách khởi tạo và truy cập phần tử
- Quản lý mảng hai chiều trong bộ nhớ
- Ví dụ ứng dụng thực tế trong chương trình
- Cách cấp phát và giải phóng mảng hai chiều động
- Các lưu ý và lỗi thường gặp khi dùng mảng
Ngay cả người mới bắt đầu cũng có thể nắm được kiến thức cơ bản về mảng hai chiều và có kỹ năng áp dụng vào các chương trình thực tế.
Đối tượng của bài viết này
Bài viết này dành cho những ai mới bắt đầu học C cũng như những lập trình viên trung cấp muốn mở rộng phạm vi ứng dụng. Nội dung đặc biệt hữu ích cho các đối tượng sau:
- Đã có hiểu biết cơ bản về mảng nhưng chưa từng sử dụng mảng hai chiều
- Muốn xử lý dữ liệu dạng bảng trong chương trình
- Muốn ôn tập lại kiến thức cơ bản của ngôn ngữ C
2. Mảng hai chiều là gì?
Trong C, “mảng hai chiều” là cấu trúc dữ liệu cho phép lưu trữ dữ liệu theo dạng hàng và cột. Nó hoạt động như một “mảng của mảng”. Đây là công cụ hữu ích để tính toán ma trận hoặc xử lý dữ liệu dạng bảng, và được sử dụng thường xuyên trong lập trình.
Tại đây, chúng ta sẽ tìm hiểu chi tiết khái niệm và cấu trúc cơ bản của mảng hai chiều.
Cấu trúc cơ bản của mảng hai chiều
Mảng hai chiều được tạo thành từ nhiều hàng và cột. Cụ thể, mỗi hàng là một mảng một chiều, và tập hợp các hàng này tạo thành mảng hai chiều.
Ví dụ: Hình dung mảng hai chiều
Dữ liệu được lưu trữ theo dạng hàng và cột như hình minh họa sau:
array[3][4] = {
{1, 2, 3, 4},
{5, 6, 7, 8},
{9, 10, 11, 12}
};
Trong trường hợp này, array
là một mảng hai chiều gồm 3 hàng và 4 cột. Có thể truy cập các phần tử cụ thể bằng chỉ số.
array[0][0]
= “1”array[2][3]
= “12”
Ứng dụng của mảng hai chiều
Mảng hai chiều được sử dụng trong nhiều tình huống, ví dụ như:
- Thao tác ma trận toán học
Ví dụ: cộng hoặc nhân ma trận - Quản lý dữ liệu dạng bảng
Ví dụ: bảng tính hoặc cấu trúc dữ liệu trong cơ sở dữ liệu - Phát triển trò chơi
Ví dụ: quản lý trạng thái bàn cờ trong cờ vua, cờ Othello - Xử lý hình ảnh
Ví dụ: lưu trữ dữ liệu màu của từng pixel
Qua các ứng dụng trên, có thể thấy mảng hai chiều là cấu trúc dữ liệu cơ bản rất quan trọng để quản lý dữ liệu phức tạp một cách hiệu quả.
Sự khác biệt giữa mảng một chiều và mảng hai chiều
Đặc điểm của mảng một chiều
Mảng một chiều lưu trữ dữ liệu theo dạng “tuyến tính”.
int array[5] = {1, 2, 3, 4, 5};
Trong trường hợp này, các phần tử được tham chiếu theo thứ tự bằng chỉ số:
array[0]
= “1”array[4]
= “5”
Đặc điểm của mảng hai chiều
Mảng hai chiều lưu trữ dữ liệu theo cả “hàng” và “cột”.
int array[2][3] = {
{1, 2, 3},
{4, 5, 6}
};
Các phần tử được tham chiếu bằng sự kết hợp giữa chỉ số hàng và cột.
array[0][2]
= “3”array[1][0]
= “4”
Như vậy, mảng hai chiều đặc biệt hữu ích khi cần xử lý các cấu trúc dữ liệu phức tạp.
3. Khai báo và khởi tạo mảng hai chiều
Trong C, để sử dụng mảng hai chiều, trước tiên phải khai báo và khởi tạo khi cần. Dưới đây là chi tiết về cách khai báo và các phương pháp khởi tạo mảng hai chiều.
Cách khai báo mảng hai chiều
Cú pháp khai báo mảng hai chiều như sau:
kiểu_dữ_liệu tên_mảng[số_hàng][số_cột];
- kiểu_dữ_liệu: loại dữ liệu của phần tử (ví dụ:
int
,float
,char
…). - số_hàng và số_cột: giá trị nguyên chỉ kích thước của mảng.
Ví dụ khai báo
Dưới đây là ví dụ khai báo mảng hai chiều kiểu int
với 3 hàng và 4 cột:
int array[3][4];
Lúc này, một vùng nhớ cho 3 hàng × 4 cột sẽ được cấp phát.
Khởi tạo mảng hai chiều
Khi khởi tạo mảng hai chiều, bạn có thể gán giá trị ban đầu ngay trong lúc khai báo. Có một số cách khởi tạo phổ biến.
Phương pháp 1: Khởi tạo tất cả phần tử một cách tường minh
Có thể khởi tạo toàn bộ phần tử như sau:
int array[2][3] = {
{1, 2, 3},
{4, 5, 6}
};
Trong trường hợp này, array
sẽ được lưu trong bộ nhớ như sau:
1 2 3
4 5 6
array[0][0]
→ 1array[1][2]
→ 6
Phương pháp 2: Chỉ khởi tạo một phần tử
Bạn cũng có thể chỉ định giá trị cho một số phần tử trong danh sách khởi tạo. Các phần tử còn lại sẽ mặc định bằng 0:
int array[2][3] = {
{1, 2},
{4}
};
Mảng này sẽ được lưu như sau:
1 2 0
4 0 0
Phương pháp 3: Khởi tạo toàn bộ bằng 0 với {0}
Nếu muốn toàn bộ phần tử đều là 0, có thể dùng cú pháp {0}
:
int array[3][4] = {0};
Tất cả phần tử của mảng sẽ được gán bằng 0.
Lưu ý khi khởi tạo
- Số hàng không thể bỏ qua
Phải chỉ định số hàng (kích thước đầu tiên) khi khai báo mảng hai chiều.
int array[][4] = {
{1, 2, 3, 4},
{5, 6, 7, 8}
};
Trong ví dụ trên, nhờ chỉ định số cột, số hàng sẽ được tự động suy ra từ danh sách khởi tạo.
- Phải chỉ định số cột
Trong mảng hai chiều, ít nhất phải khai báo số cột (kích thước thứ hai).
Ví dụ mã: Khai báo và khởi tạo mảng
Dưới đây là ví dụ khai báo, khởi tạo và in ra các phần tử của mảng hai chiều:
#include <stdio.h>
int main() {
int array[2][3] = {
{1, 2, 3},
{4, 5, 6}
};
for (int i = 0; i < 2; i++) {
for (int j = 0; j < 3; j++) {
printf("array[%d][%d] = %d\n", i, j, array[i][j]);
}
}
return 0;
}
Kết quả xuất ra
array[0][0] = 1
array[0][1] = 2
array[0][2] = 3
array[1][0] = 4
array[1][1] = 5
array[1][2] = 6
4. Cách sử dụng mảng hai chiều: Truy cập và thao tác phần tử
Trong C, bạn có thể truy cập và thay đổi giá trị của từng phần tử trong mảng hai chiều. Dưới đây là cách truy cập và thao tác cụ thể.
Truy cập phần tử
Để truy cập một phần tử trong mảng hai chiều, bạn chỉ định số hàng và số cột.
Cú pháp cơ bản
array[số_hàng][số_cột]
Ví dụ với mảng dưới đây:
int array[2][3] = {
{1, 2, 3},
{4, 5, 6}
};
array[0][0]
→ 1 (phần tử hàng 1, cột 1)array[1][2]
→ 6 (phần tử hàng 2, cột 3)
Ví dụ: In ra phần tử
#include <stdio.h>
int main() {
int array[2][3] = {
{1, 2, 3},
{4, 5, 6}
};
printf("array[0][0] = %d\n", array[0][0]);
printf("array[1][2] = %d\n", array[1][2]);
return 0;
}
Kết quả xuất ra
array[0][0] = 1
array[1][2] = 6
Thao tác trên phần tử
Bạn có thể gán giá trị mới để thay đổi phần tử trong mảng.
Cách thay đổi giá trị
array[số_hàng][số_cột] = giá_trị_mới;
Ví dụ: Chương trình thay đổi giá trị
#include <stdio.h>
int main() {
int array[2][3] = {
{1, 2, 3},
{4, 5, 6}
};
// Thay đổi giá trị
array[0][0] = 10;
array[1][2] = 20;
// In kết quả
for (int i = 0; i < 2; i++) {
for (int j = 0; j < 3; j++) {
printf("array[%d][%d] = %d\n", i, j, array[i][j]);
}
}
return 0;
}
Kết quả xuất ra
array[0][0] = 10
array[0][1] = 2
array[0][2] = 3
array[1][0] = 4
array[1][1] = 5
array[1][2] = 20
Vòng lặp với mảng hai chiều
Khi thao tác với mảng hai chiều, thường sử dụng vòng lặp lồng nhau.
Ví dụ: Duyệt hàng và cột bằng vòng lặp
#include <stdio.h>
int main() {
int array[2][3] = {
{1, 2, 3},
{4, 5, 6}
};
// In các phần tử
for (int i = 0; i < 2; i++) { // duyệt hàng
for (int j = 0; j < 3; j++) { // duyệt cột
printf("array[%d][%d] = %d\n", i, j, array[i][j]);
}
}
return 0;
}
Kết quả xuất ra
array[0][0] = 1
array[0][1] = 2
array[0][2] = 3
array[1][0] = 4
array[1][1] = 5
array[1][2] = 6
Ví dụ ứng dụng: Gán giá trị cố định cho toàn bộ phần tử
Bạn cũng có thể dùng vòng lặp để gán toàn bộ phần tử một giá trị nhất định.
Ví dụ: Gán toàn bộ phần tử bằng 5
#include <stdio.h>
int main() {
int array[3][3];
// Gán tất cả phần tử bằng 5
for (int i = 0; i < 3; i++) {
for (int j = 0; j < 3; j++) {
array[i][j] = 5;
}
}
// In kết quả
for (int i = 0; i < 3; i++) {
for (int j = 0; j < 3; j++) {
printf("array[%d][%d] = %d\n", i, j, array[i][j]);
}
}
return 0;
}
Kết quả xuất ra
array[0][0] = 5
array[0][1] = 5
array[0][2] = 5
array[1][0] = 5
array[1][1] = 5
array[1][2] = 5
array[2][0] = 5
array[2][1] = 5
array[2][2] = 5
5. Cấu trúc bộ nhớ của mảng hai chiều
Khi lập trình C, việc hiểu cách mảng hai chiều được bố trí trong bộ nhớ rất quan trọng. Kiến thức này giúp tăng hiệu quả chương trình và dễ dàng thao tác bằng con trỏ.
Dưới đây là giải thích chi tiết về cấu trúc bộ nhớ của mảng hai chiều.
Cách sắp xếp bộ nhớ
Trong C, mảng hai chiều thực chất được lưu liên tiếp trong bộ nhớ dưới dạng một chiều. Phương pháp này gọi là row-major (ưu tiên hàng).
Row-major là gì?
Row-major nghĩa là dữ liệu của mảng được lưu liên tục theo từng hàng.
Ví dụ: Bố trí bộ nhớ
Xét mảng sau:
int array[2][3] = {
{1, 2, 3},
{4, 5, 6}
};
Mảng này sẽ được lưu trong bộ nhớ như sau:
Bộ nhớ: 1 2 3 4 5 6
array[0][0]
→ vị trí đầu tiên trong bộ nhớarray[0][1]
→ vị trí thứ haiarray[1][0]
→ vị trí thứ tư
Tham chiếu phần tử bằng chỉ số
Khi sử dụng chỉ số, trong C bạn có thể tham chiếu đến phần tử của mảng theo công thức sau:
array[i][j] = *(array + (i * số_cột) + j)
Ví dụ: Tính toán địa chỉ trong bộ nhớ
Với mảng array[2][3]
ở trên:
array[1][2]
được tính như sau:
*(array + (1 * 3) + 2) = *(array + 5)
Điều này có nghĩa là, do mảng được lưu theo hàng, chương trình sẽ bỏ qua 3 phần tử của hàng đầu tiên (i = 1
) rồi cộng thêm chỉ số cột (j = 2
) để đến phần tử cần tham chiếu.
Sử dụng con trỏ với mảng hai chiều
Mảng hai chiều trong C cũng có thể thao tác bằng con trỏ, điều này giúp linh hoạt hơn.
Quan hệ giữa con trỏ và mảng hai chiều
Vì mảng hai chiều là “mảng của mảng”, nên ta có thể sử dụng con trỏ để truy cập các phần tử:
int array[2][3] = {
{1, 2, 3},
{4, 5, 6}
};
int *ptr = &array[0][0];
printf("%d\n", *(ptr + 4)); // Kết quả: 5
Ở đây, con trỏ ptr
trỏ đến phần tử đầu tiên của mảng, và bằng cách tăng chỉ số, có thể truy cập các phần tử khác trong mảng.
Hình dung cấu trúc bộ nhớ
Sơ đồ dưới đây mô tả bố trí bộ nhớ của array[2][3]
:
Bộ nhớ: 1 2 3 4 5 6
Chỉ số:
[0][0] [0][1] [0][2] [1][0] [1][1] [1][2]
Như vậy, mảng hai chiều được lưu liên tục trong bộ nhớ như một mảng một chiều.
Lưu ý để thao tác hiệu quả
Để thao tác mảng hai chiều một cách tối ưu, cần chú ý các điểm sau:
- Truy cập theo hàng
Khi duyệt mảng, cố định hàng và thay đổi cột trong vòng lặp sẽ hiệu quả hơn.
for (int i = 0; i < số_hàng; i++) {
for (int j = 0; j < số_cột; j++) {
// Truy cập theo hàng
}
}
Cách này tận dụng được cache của bộ nhớ, giúp chương trình chạy nhanh hơn.
- Tận dụng con trỏ
Dùng con trỏ có thể giảm thao tác tính toán chỉ số, tiết kiệm chi phí xử lý.
6. Ví dụ thực tế: Phép toán ma trận và tạo bàn cờ game
Mảng hai chiều thường được áp dụng trong các chương trình thực tế như phép toán ma trận hoặc quản lý trạng thái bàn cờ trong game. Ở đây chúng ta sẽ xem hai ví dụ: “phép toán ma trận” và “tạo bàn cờ”.
Ví dụ về phép toán ma trận
Phép toán ma trận được dùng nhiều trong toán học và kỹ thuật. Với mảng hai chiều, bạn có thể dễ dàng thực hiện phép cộng và nhân ma trận.
Ví dụ 1: Cộng ma trận
Chương trình cộng hai ma trận được viết như sau:
#include <stdio.h>
int main() {
// Hai ma trận 3x3
int matrix1[3][3] = {
{1, 2, 3},
{4, 5, 6},
{7, 8, 9}
};
int matrix2[3][3] = {
{9, 8, 7},
{6, 5, 4},
{3, 2, 1}
};
int result[3][3];
// Cộng ma trận
for (int i = 0; i < 3; i++) {
for (int j = 0; j < 3; j++) {
result[i][j] = matrix1[i][j] + matrix2[i][j];
}
}
// In kết quả
printf("Kết quả cộng ma trận:\n");
for (int i = 0; i < 3; i++) {
for (int j = 0; j < 3; j++) {
printf("%d ", result[i][j]);
}
printf("\n");
}
return 0;
}
Kết quả xuất ra
Kết quả cộng ma trận:
10 10 10
10 10 10
10 10 10
Trong ví dụ này, hai ma trận 3×3 được cộng từng phần tử, và kết quả được lưu vào ma trận mới.
Ví dụ 2: Nhân ma trận
Chương trình thực hiện phép nhân ma trận được viết như sau:
#include <stdio.h>
int main() {
// Hai ma trận 3x3
int matrix1[3][3] = {
{1, 2, 3},
{4, 5, 6},
{7, 8, 9}
};
int matrix2[3][3] = {
{9, 8, 7},
{6, 5, 4},
{3, 2, 1}
};
int result[3][3] = {0};
// Nhân ma trận
for (int i = 0; i < 3; i++) {
for (int j = 0; j < 3; j++) {
for (int k = 0; k < 3; k++) {
result[i][j] += matrix1[i][k] * matrix2[k][j];
}
}
}
// In kết quả
printf("Kết quả nhân ma trận:\n");
for (int i = 0; i < 3; i++) {
for (int j = 0; j < 3; j++) {
printf("%d ", result[i][j]);
}
printf("\n");
}
return 0;
}
Kết quả xuất ra
Kết quả nhân ma trận:
30 24 18
84 69 54
138 114 90
Ví dụ tạo bàn cờ game
Mảng hai chiều thường được dùng để quản lý bàn cờ trong các trò chơi. Ví dụ dưới đây minh họa cách khởi tạo một bàn cờ Othello đơn giản.
Ví dụ: Khởi tạo và in bàn cờ Othello
#include <stdio.h>
int main() {
// Khởi tạo bàn cờ Othello 8x8
int board[8][8] = {0};
// Thiết lập trạng thái ban đầu
board[3][3] = 1; // Trắng
board[3][4] = 2; // Đen
board[4][3] = 2; // Đen
board[4][4] = 1; // Trắng
// In bàn cờ
printf("Trạng thái ban đầu của bàn cờ Othello:\n");
for (int i = 0; i < 8; i++) {
for (int j = 0; j < 8; j++) {
printf("%d ", board[i][j]);
}
printf("\n");
}
return 0;
}
Kết quả xuất ra
Trạng thái ban đầu của bàn cờ Othello:
0 0 0 0 0 0 0 0
0 0 0 0 0 0 0 0
0 0 0 0 0 0 0 0
0 0 0 1 2 0 0 0
0 0 0 2 1 0 0 0
0 0 0 0 0 0 0 0
0 0 0 0 0 0 0 0
0 0 0 0 0 0 0 0
Trong chương trình này, số 0 biểu thị ô trống, số 1 là quân trắng, và số 2 là quân đen. Kết quả được in ra màn hình theo trạng thái ban đầu của bàn cờ.
7. Quan hệ giữa mảng hai chiều và con trỏ
Trong ngôn ngữ C, mảng hai chiều và con trỏ có quan hệ chặt chẽ. Nhờ con trỏ, bạn có thể thao tác mảng hai chiều một cách hiệu quả. Phần này sẽ giải thích từ khái niệm cơ bản đến ví dụ ứng dụng thực tế.
Quan hệ cơ bản giữa mảng hai chiều và con trỏ
Mảng hai chiều thực chất được cài đặt như một “mảng của mảng”. Do đó, mỗi hàng có thể được coi là một con trỏ.
Ví dụ: Cấu trúc cơ bản của mảng hai chiều
Khai báo một mảng hai chiều như sau:
int array[2][3] = {
{1, 2, 3},
{4, 5, 6}
};
Mảng này sẽ được bố trí trong bộ nhớ như sau:
[1] [2] [3] [4] [5] [6]
array
là con trỏ trỏ tới phần tử đầu tiên của mảng.array[i]
là con trỏ trỏ tới hàng thứ i.array[i][j]
là phần tử thực tế.
Tham chiếu phần tử bằng con trỏ
Bạn có thể sử dụng phép toán con trỏ để tham chiếu đến các phần tử trong mảng, ví dụ như sau:
*(array[0] + 1) // tương đương array[0][1]
*(*(array + 1) + 2) // tương đương array[1][2]
Xử lý mảng hai chiều như con trỏ
Khi truyền mảng hai chiều vào hàm, sử dụng con trỏ sẽ tiện lợi hơn. Ví dụ sau minh họa cách xử lý mảng hai chiều trong hàm.
Ví dụ: Xử lý mảng hai chiều trong hàm
#include <stdio.h>
// Định nghĩa hàm
void printArray(int (*array)[3], int rows) {
for (int i = 0; i < rows; i++) {
for (int j = 0; j < 3; j++) {
printf("%d ", array[i][j]);
}
printf("\n");
}
}
int main() {
int array[2][3] = {
{1, 2, 3},
{4, 5, 6}
};
// Truyền mảng vào hàm
printArray(array, 2);
return 0;
}
Kết quả xuất ra
1 2 3
4 5 6
Điểm cần lưu ý
int (*array)[3]
biểu thị một con trỏ tới mảng có 3 cột.- Bên trong hàm có thể truy cập phần tử bằng chỉ số hàng và cột.
Tạo mảng hai chiều động bằng con trỏ
Bạn cũng có thể tạo mảng hai chiều động bằng cách sử dụng con trỏ, giúp chương trình linh hoạt hơn.
Ví dụ: Cấp phát động mảng hai chiều
#include <stdio.h>
#include <stdlib.h>
int main() {
int rows = 2, cols = 3;
// Cấp phát bộ nhớ động
int** array = malloc(rows * sizeof(int*));
for (int i = 0; i < rows; i++) {
array[i] = malloc(cols * sizeof(int));
}
// Gán giá trị cho mảng
for (int i = 0; i < rows; i++) {
for (int j = 0; j < cols; j++) {
array[i][j] = i * cols + j + 1;
}
}
// In mảng
for (int i = 0; i < rows; i++) {
for (int j = 0; j < cols; j++) {
printf("%d ", array[i][j]);
}
printf("\n");
}
// Giải phóng bộ nhớ
for (int i = 0; i < rows; i++) {
free(array[i]);
}
free(array);
return 0;
}
Kết quả xuất ra
1 2 3
4 5 6
8. Cách cấp phát và giải phóng mảng hai chiều động
Trong C, bạn có thể sử dụng cấp phát bộ nhớ động để tạo mảng hai chiều với kích thước xác định trong lúc chạy chương trình. Điều này rất hữu ích khi kích thước mảng không cố định.
Nguyên tắc cơ bản của cấp phát động
Để cấp phát bộ nhớ động, sử dụng các hàm như malloc
hoặc calloc
. Điều này cho phép bạn quyết định kích thước mảng khi chương trình đang chạy.
Hai cách tạo mảng hai chiều động
Có hai cách phổ biến để tạo mảng hai chiều động:
- Sử dụng mảng con trỏ
- Sử dụng mảng một chiều phẳng (flat) để giả lập hai chiều
Cách 1: Sử dụng mảng con trỏ
Với cách này, cấp phát bộ nhớ riêng cho từng hàng.
Các bước
- Cấp phát mảng con trỏ theo số hàng.
- Cấp phát bộ nhớ cho từng hàng theo số cột.
Ví dụ: Tạo và sử dụng mảng hai chiều động
#include <stdio.h>
#include <stdlib.h>
int main() {
int rows = 3, cols = 4;
// Cấp phát con trỏ theo số hàng
int** array = malloc(rows * sizeof(int*));
// Cấp phát bộ nhớ cho từng hàng
for (int i = 0; i < rows; i++) {
array[i] = malloc(cols * sizeof(int));
}
// Gán giá trị
for (int i = 0; i < rows; i++) {
for (int j = 0; j < cols; j++) {
array[i][j] = i * cols + j + 1;
}
}
// In mảng
printf("Mảng hai chiều động:\n");
for (int i = 0; i < rows; i++) {
for (int j = 0; j < cols; j++) {
printf("%d ", array[i][j]);
}
printf("\n");
}
// Giải phóng bộ nhớ
for (int i = 0; i < rows; i++) {
free(array[i]);
}
free(array);
return 0;
}
Kết quả xuất ra
Mảng hai chiều động:
1 2 3 4
5 6 7 8
9 10 11 12
Cách 2: Sử dụng mảng một chiều phẳng
Với cách này, toàn bộ mảng hai chiều sẽ được cấp phát dưới dạng một mảng một chiều, sau đó sử dụng phép toán chỉ số để truy cập như mảng hai chiều.
Các bước
- Cấp phát bộ nhớ theo kích thước = số hàng × số cột.
- Dùng công thức tính chỉ số để tham chiếu phần tử.
Ví dụ: Tạo mảng hai chiều từ mảng một chiều phẳng
#include <stdio.h>
#include <stdlib.h>
int main() {
int rows = 3, cols = 4;
// Cấp phát một lần cho toàn bộ mảng
int* array = malloc(rows * cols * sizeof(int));
// Gán giá trị
for (int i = 0; i < rows; i++) {
for (int j = 0; j < cols; j++) {
array[i * cols + j] = i * cols + j + 1;
}
}
// In mảng
printf("Mảng hai chiều sử dụng mảng một chiều phẳng:\n");
for (int i = 0; i < rows; i++) {
for (int j = 0; j < cols; j++) {
printf("%d ", array[i * cols + j]);
}
printf("\n");
}
// Giải phóng bộ nhớ
free(array);
return 0;
}
Kết quả xuất ra
Mảng hai chiều sử dụng mảng một chiều phẳng:
1 2 3 4
5 6 7 8
9 10 11 12
Lưu ý khi cấp phát động
- Tránh rò rỉ bộ nhớ
Nếu quên giải phóng, sẽ xảy ra rò rỉ bộ nhớ. Luôn dùngfree
để giải phóng. - Kiểm tra lỗi khi cấp phát
Khimalloc
hoặccalloc
thất bại, chúng trả vềNULL
. Luôn cần kiểm tra kết quả.
if (array == NULL) {
printf("Cấp phát bộ nhớ thất bại.\n");
return 1;
}
- Cẩn thận khi tính toán kích thước
Hãy đảm bảo tính đúng số ô cần thiết để cấp phát bộ nhớ chính xác.
9. Những lưu ý khi sử dụng mảng hai chiều
Mảng hai chiều rất tiện lợi, nhưng nếu sử dụng sai có thể gây ra lỗi như bug hoặc rò rỉ bộ nhớ. Dưới đây là những điểm cần chú ý và lỗi thường gặp.
Tránh truy cập ngoài phạm vi
Nếu truy cập ngoài phạm vi của mảng, chương trình có thể hoạt động sai hoặc bị crash.
Ví dụ: Lỗi truy cập ngoài phạm vi
#include <stdio.h>
int main() {
int array[2][3] = {
{1, 2, 3},
{4, 5, 6}
};
// Truy cập ngoài phạm vi (sai)
printf("%d\n", array[2][0]); // Không tồn tại hàng thứ 3
return 0;
}
Ở chương trình trên, array[2][0]
tham chiếu đến một vùng nhớ không tồn tại, gây hành vi không xác định.
Cách phòng tránh
- Đặt điều kiện vòng lặp sao cho không vượt quá kích thước hàng và cột.
- Cài đặt kiểm tra biên khi cần thiết.
Ví dụ sửa đúng
for (int i = 0; i < 2; i++) { // duyệt đúng số hàng
for (int j = 0; j < 3; j++) {
printf("%d ", array[i][j]);
}
}
Tránh rò rỉ bộ nhớ
Nếu không giải phóng mảng động, sẽ gây rò rỉ bộ nhớ.
Ví dụ: Rò rỉ bộ nhớ
Dưới đây là ví dụ không giải phóng đủ bộ nhớ:
#include <stdlib.h>
int main() {
int rows = 2, cols = 3;
// Cấp phát động
int** array = malloc(rows * sizeof(int*));
for (int i = 0; i < rows; i++) {
array[i] = malloc(cols * sizeof(int));
}
// Quên giải phóng từng hàng
free(array);
return 0;
}
Trong đoạn code trên, bộ nhớ cấp phát cho từng hàng không được giải phóng, dẫn đến rò rỉ bộ nhớ.
Cách phòng tránh
Luôn giải phóng theo đúng thứ tự:
- Giải phóng từng hàng.
- Giải phóng mảng con trỏ.
Cách giải phóng đúng
for (int i = 0; i < rows; i++) {
free(array[i]); // Giải phóng từng hàng
}
free(array); // Giải phóng mảng con trỏ
Hạn chế trong việc thay đổi kích thước
Trong C, mảng tĩnh không thể thay đổi kích thước sau khi đã khai báo. Nếu muốn linh hoạt về kích thước, cần sử dụng mảng động.
Cách khắc phục
- Sử dụng mảng động khi cần thay đổi kích thước.
- Nếu cần thay đổi thường xuyên, có thể dùng
realloc
để cấp phát lại bộ nhớ.
Khởi tạo mảng
Nếu quên khởi tạo, mảng có thể chứa “giá trị rác” (garbage value) từ bộ nhớ chưa được gán.
Ví dụ: Quên khởi tạo
int array[2][3];
printf("%d\n", array[0][0]); // Có thể in ra giá trị rác
Cách phòng tránh
- Khởi tạo ngay khi khai báo.
int array[2][3] = {0}; // Khởi tạo tất cả phần tử bằng 0
- Với mảng động, dùng
calloc
để khởi tạo với giá trị 0.
int* array = calloc(rows * cols, sizeof(int));
Hiệu suất bộ nhớ và bộ nhớ đệm (cache)
Để truy cập mảng hiệu quả, cần duyệt theo thứ tự hàng (row-major).
Ví dụ: Truy cập theo hàng
for (int i = 0; i < rows; i++) {
for (int j = 0; j < cols; j++) {
printf("%d ", array[i][j]);
}
}
Truy cập theo cột kém hiệu quả
Nếu duyệt theo cột trước, hiệu quả cache giảm và tốc độ chương trình có thể chậm hơn.
Danh sách lỗi thường gặp
Khi làm việc với mảng hai chiều, hãy kiểm tra các lỗi phổ biến sau:
- Có truy cập ngoài phạm vi không?
- Có giải phóng đầy đủ bộ nhớ động không?
- Có khởi tạo mảng trước khi sử dụng không?
- Nếu cần thay đổi kích thước, có dùng
realloc
đúng cách không? - Có duyệt theo hàng để tận dụng cache không?

10. Tổng kết
Bài viết này đã giải thích chi tiết về mảng hai chiều trong C, từ cơ bản đến nâng cao. Mảng hai chiều là cấu trúc dữ liệu mạnh mẽ, hữu ích trong các tình huống như phép toán ma trận, quản lý dữ liệu, hay tạo bàn cờ trong trò chơi. Hãy cùng điểm lại các ý chính.
1. Cấu trúc cơ bản của mảng hai chiều
- Mảng hai chiều được tạo từ “hàng” và “cột”.
- Cách khai báo cơ bản:
int array[số_hàng][số_cột];
- Truy cập phần tử bằng chỉ số:
array[i][j];
2. Khởi tạo và thao tác phần tử
- Có nhiều cách khởi tạo:
- Khởi tạo tường minh:
int array[2][3] = { {1, 2, 3}, {4, 5, 6} };
- Khởi tạo toàn bộ bằng 0:
int array[2][3] = {0};
- Dùng vòng lặp để thao tác hiệu quả.
3. Quan hệ với bộ nhớ và con trỏ
- Mảng hai chiều được lưu theo dạng row-major trong bộ nhớ.
- Có thể sử dụng con trỏ để thao tác:
*(*(array + i) + j);
- Khi truyền mảng vào hàm, cần chỉ định số cột:
void printArray(int (*array)[số_cột], int rows);
4. Cấp phát và giải phóng động
- Có thể tạo mảng động bằng cách dùng con trỏ.
- Ví dụ với mảng con trỏ:
int** array = malloc(rows * sizeof(int*));
for (int i = 0; i < rows; i++) {
array[i] = malloc(cols * sizeof(int));
}
- Luôn giải phóng bộ nhớ đúng cách:
for (int i = 0; i < rows; i++) {
free(array[i]);
}
free(array);
5. Những điểm cần chú ý
- Tránh truy cập ngoài phạm vi.
- Không sử dụng mảng chưa khởi tạo.
- Luôn giải phóng bộ nhớ khi dùng mảng động.
Ứng dụng của mảng hai chiều
Mảng hai chiều hữu ích trong nhiều lĩnh vực:
- Phép toán ma trận: Xử lý tính toán nhanh chóng.
- Lập trình game: Quản lý trạng thái bàn cờ (cờ vua, Othello, v.v.).
- Xử lý dữ liệu: Quản lý bảng tính hoặc dữ liệu dạng bảng.
- Xử lý ảnh: Lưu trữ dữ liệu pixel.
Bước tiếp theo
Sau khi nắm được mảng hai chiều, bạn có thể nâng cao kỹ năng lập trình C bằng cách học các chủ đề tiếp theo:
- Mảng đa chiều: như mảng 3 chiều hoặc hơn.
- Ứng dụng con trỏ: thao tác hiệu quả hơn với mảng.
- Quản lý bộ nhớ nâng cao: sử dụng
realloc
để thay đổi kích thước mảng, và các kỹ thuật quản lý bộ nhớ khác.
Kết luận
Bằng việc học mảng hai chiều từ cơ bản đến ứng dụng, bạn đã có thêm công cụ mạnh mẽ để viết chương trình hiệu quả hơn. Hãy luyện tập nhiều lần, và tham khảo tài liệu chính thức hoặc các nguồn học tập khác để nâng cao kỹ năng C của bạn.