Khởi tạo mảng
Sau bài này bạn sẽ làm được
- Chọn đúng cách khởi tạo cho từng tình huống
- Dùng khởi tạo có chỉ định để lập bảng tra
- Sao chép mảng bằng memcpy hoặc vòng lặp
- Hiểu vì sao mảng có độ dài thay đổi bị cấm trong khóa học này
Một mảng chưa khởi tạo chứa rác, và rác trong mảng khó phát hiện hơn rác trong biến đơn rất nhiều vì bạn thường chỉ nhìn vài phần tử đầu. Bài này đi hết mọi cách khởi tạo mảng mà C cho phép.
#Sáu cách khởi tạo
int a[5] = { 1, 2, 3, 4, 5 }; /* 1. đầy đủ */
int b[5] = { 1, 2 }; /* 2. thiếu, phần còn lại tự bằng 0 */
int c[5] = { 0 }; /* 3. thành ngữ: cả mảng bằng 0 */
int d[] = { 1, 2, 3 }; /* 4. tự suy kích thước, ở đây là 3 */
int e[5] = { }; /* 5. C23, cả mảng bằng 0 */
int f[5]; /* 6. KHÔNG khởi tạo, chứa rác */| Cách viết | Nội dung mảng | Khi nào dùng |
|---|---|---|
| { 1, 2, 3, 4, 5 } | 1 2 3 4 5 | Biết trước toàn bộ giá trị |
| { 1, 2 } | 1 2 0 0 0 | Chỉ vài phần tử đầu có giá trị riêng |
| { 0 } | 0 0 0 0 0 | Muốn cả mảng bằng 0, cách phổ biến nhất |
| { 1 } | 1 0 0 0 0 | Cẩn thận, chỉ phần tử đầu bằng 1 |
| không có gì | rác rác rác rác rác | Chỉ khi bạn sẽ ghi đè toàn bộ ngay sau đó |
Vì sao mảng toàn cục thì khác
int g[5]; /* toàn cục: chắc chắn 0 0 0 0 0 */
int main(void)
{
int l[5]; /* cục bộ: rác */
return 0;
}Lý do đã nói ở Bài 8.8: mảng toàn cục nằm ở vùng .bss và được hệ điều hành xóa sạch lúc nạp. Mảng cục bộ nằm trên ngăn xếp, và ngăn xếp chứa những gì lời gọi hàm trước để lại.
void *memset(void *s, int c, size_t n);
#include <string.h>
int a[1000];
memset(a, 0, sizeof a); /* điền 0 vào toàn bộ mảng */#Khởi tạo có chỉ định
int a[10] = { [0] = 1, [5] = 50, [9] = 90 };
/* kết quả: 1 0 0 0 0 50 0 0 0 90 */Cú pháp này tỏa sáng khi lập bảng tra. So sánh hai cách viết cùng một bảng số ngày trong tháng:
/* Phần tử đầu là số 0 giả để tháng 1 rơi vào chỉ số 1.
Ai đọc cũng phải đếm ngón tay để biết 30 nào là tháng nào. */
int ngay[13] = { 0, 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 };int ngay[13] = {
[1] = 31, [2] = 28, [3] = 31, [4] = 30,
[5] = 31, [6] = 30, [7] = 31, [8] = 31,
[9] = 30, [10] = 31, [11] = 30, [12] = 31,
};
/* Chỉ số 0 tự bằng 0, và mỗi dòng tự nói lên tháng nào bao nhiêu ngày. */Ba ứng dụng thường gặp
/* 1. Bảng phân loại ký tự, 256 phần tử nhưng chỉ vài chỗ khác 0 */
int la_toan_tu[256] = {
['+'] = 1, ['-'] = 1, ['*'] = 1, ['/'] = 1, ['%'] = 1,
};
if (la_toan_tu[(unsigned char)c])
xu_ly_toan_tu(c);
/* 2. Bảng ánh xạ mã lỗi sang thông báo */
const char *thong_bao[8] = {
[0] = "Thanh cong",
[1] = "Khong mo duoc file",
[2] = "Het bo nho",
[7] = "Loi khong xac dinh",
};
/* 3. Bảng tra thay cho chuỗi if else if dài */
int diem_chu[128] = {
['A'] = 4, ['B'] = 3, ['C'] = 2, ['D'] = 1, ['F'] = 0,
};#Khởi tạo mảng ký tự
char s1[6] = "hello"; /* {'h','e','l','l','o','\0'} cần đúng 6 ô */
char s2[] = "hello"; /* kích thước tự suy ra là 6 */
char s3[5] = "hello"; /* hợp lệ nhưng KHÔNG có ký tự kết thúc */
char s4[10] = "hi"; /* {'h','i','\0',0,0,0,0,0,0,0} */#Không gán mảng bằng dấu bằng
int a[5] = { 1, 2, 3, 4, 5 };
int b[5];
b = a; /* lỗi biên dịch: assignment to expression with array type */Lý do là cơ chế phân rã mảng ở Bài 8.7. Trong biểu thức b = a, vế phải a phân rã thành con trỏ, còn vế trái b không phải một biến có thể gán. Mảng là một vùng nhớ có tên, không phải một giá trị bạn di chuyển được.
| Cách sao chép | Viết thế nào | Ghi chú |
|---|---|---|
| Vòng lặp | for (i = 0; i < n; ++i) b[i] = a[i]; | Rõ ràng, hoạt động với mọi kiểu |
| memcpy | memcpy(b, a, sizeof a); | Nhanh nhất, hai vùng không được chồng lấn |
| memmove | memmove(b, a, sizeof a); | Chậm hơn chút, an toàn cả khi hai vùng chồng lấn |
| Bọc trong struct | typedef struct { int v[5]; } Mang5; | Struct thì gán bằng dấu bằng được |
#include <string.h>
int a[5] = { 1, 2, 3, 4, 5 };
int b[5];
memcpy(b, a, sizeof a); /* dùng sizeof a, KHÔNG phải sizeof b */
/* Mẹo: mảng bọc trong struct thì gán được */
typedef struct { int v[5]; } Mang5;
Mang5 x = { { 1, 2, 3, 4, 5 } };
Mang5 y = x; /* hợp lệ, chép cả 20 byte */#Mảng có độ dài thay đổi
int n;
scanf("%d", &n);
int a[n]; /* mảng có độ dài thay đổi, tính năng của C99 */int n;
if (scanf("%d", &n) != 1) return 1;
int a[n]; /* n = 10000000 là sập, không cứu được */int n;
if (scanf("%d", &n) != 1 || n <= 0 || n > 1000000) {
fprintf(stderr, "n phai trong khoang 1 toi 1000000\n");
return 1;
}
int *a = malloc((size_t)n * sizeof *a);
if (a == NULL) {
fprintf(stderr, "Khong du bo nho\n");
return 1;
}
/* ... dùng a ... */
free(a);Cho tới khi học Chương 14, cách làm trong khóa này là khai báo mảng đủ lớn với một hằng số và kiểm tra dữ liệu vào không vượt quá nó.
enum { N_TOI_DA = 1000 };
int a[N_TOI_DA];
size_t n;
if (scanf("%zu", &n) != 1 || n == 0 || n > N_TOI_DA) {
fprintf(stderr, "So phan tu phai tu 1 toi %d\n", N_TOI_DA);
return 1;
}Tự làm thử
- In toàn bộ nội dung của
int a[5] = { 1, 2 };vàint b[5];ở dạng cục bộ, so sánh kết quả. - Dùng
memsetđiền giá trị 1 vào mảngint, in ra và giải thích con số nhận được. - Viết bảng tra số ngày trong tháng bằng khởi tạo có chỉ định, kèm xử lý năm nhuận.
- Lập bảng
int la_nguyen_am[256]bằng khởi tạo có chỉ định, dùng nó để đếm nguyên âm trong một chuỗi. - Thử gán
b = agiữa hai mảng, chép lại thông báo lỗi, rồi sửa bằng cả ba cách trong bảng. - Viết chương trình dùng mảng độ dài thay đổi với n nhập từ bàn phím, thử n bằng mười triệu và quan sát kết quả.
Trình chấm điểm tự động sẽ được bổ sung ở giai đoạn sau. Hiện tại bạn tự chạy thử trên máy.
Tóm tắt
- Nếu có bất kỳ giá trị khởi tạo nào, mọi phần tử không được nhắc tới đều bằng 0. Đó là lý do
{ 0 }xóa sạch mảng. { 1 }chỉ làm phần tử đầu bằng 1, không làm cả mảng bằng 1.- Khởi tạo có chỉ định của C99 làm bảng tra dễ đọc và không thể đếm lệch vị trí.
memsetđiền theo byte, chỉ dùng an toàn với 0 và âm một.- Mảng không gán được bằng dấu bằng. Dùng vòng lặp hoặc
memcpyvới đối số thứ ba tính bằng byte. - Không dùng mảng có độ dài thay đổi. Nó nằm trên ngăn xếp và không có cách kiểm tra thất bại.