1. Giới thiệu
Ngôn ngữ C vẫn được sử dụng rộng rãi trong các dự án phát triển hệ thống và nhúng. Trong số đó, “mảng char” là một trong những thành phần cú pháp cơ bản và quan trọng nhất để xử lý chuỗi.
Ngôn ngữ C không có kiểu chuỗi tiêu chuẩn. Thay vào đó, sử dụng mảng ký tự (mảng char) để biểu diễn chuỗi. Điều này không thực sự trực quan đối với người mới bắt đầu, vì vậy việc hiểu mảng char thường trở thành rào cản lớn trong việc học C.
Ngoài ra, nếu không nắm rõ cách phân biệt mảng char và con trỏ char ( char*
), cũng như sự tồn tại của ký tự null ( \0
), bạn sẽ dễ gặp các lỗi không mong muốn.
Bài viết này tập trung vào chủ đề “C ngôn ngữ char mảng”, giải thích một cách dễ hiểu từ cách sử dụng cơ bản, các kỹ thuật nâng cao, cho đến cách tránh các lỗi thường gặp.
Nếu bạn đang muốn học ngôn ngữ C một cách nghiêm túc hoặc muốn ôn lại mảng char, hãy đọc đến cuối bài. Trong chương tiếp theo, chúng tôi sẽ giải thích định nghĩa và cơ chế cơ bản của mảng char.
2. Mảng char là gì?
Trong ngôn ngữ C, “mảng char” là một mảng dùng để lưu trữ nhiều ký tự (kiểu char) đó. Đây là cấu trúc cơ bản khi làm việc với chuỗi ký tự.
Kiểu char là gì?
Trong ngôn ngữ C, char
là kiểu dữ liệu dùng để biểu diễn một ký tự. Ví dụ, có thể định nghĩa một biến ký tự như sau.
char c = 'A';
Như vậy, 'A'
dạng dấu nháy đơn bao quanh 1 ký tự được định nghĩa là kiểu char
.
Cú pháp cơ bản của mảng char
Để lưu trữ nhiều ký tự, sử dụng mảng kiểu char
. Định nghĩa như sau:
char str[6] = {'H', 'e', 'l', 'l', 'o', '\0'};
Mảng này cấp phát vùng nhớ đủ cho 6 ký tự, bao gồm chuỗi 5 ký tự "Hello"
và ký tự null (\0
).
Tầm quan trọng của ký tự null('Kích thước mảng và lưu ý
'
)
Kích thước mảng và lưu ý
'Trong ngôn ngữ C, để chỉ kết thúc chuỗi, sử dụng ký tự null '\0'
. Nếu không có ký tự này, các hàm xử lý chuỗi sẽ không hoạt động đúng và có thể gây ra hành vi không mong muốn.
char str[] = "Hello"; // tự động thêm '\0' vào cuối
Như trên, nếu sử dụng chuỗi ký tự được bao quanh bởi dấu ngoặc kép, trình biên dịch sẽ tự động thêm '\0'
vào cuối.
3. khai báo và khởi tạo mảng char
Khi sử dụng mảng char, cơ bản là phải cấp phát kích thước số ký tự cần thiết + 1 (cho ký tự null). Nếu gán chuỗi vào mảng có kích thước không đủ, sẽ gây tràn bộ đệm và có thể làm chương trình kết thúc bất thường.
Khai báo và khởi tạo tĩnh
Để sử dụng mảng char, trước tiên cần có khai báo và khởi tạo thích hợp. Ở đây, chúng tôi sẽ giải thích từ cách khai báo cơ bản của mảng char trong ngôn ngữ C, cách khởi tạo, và cả việc sử dụng bộ nhớ động.
Khởi tạo bằng literal chuỗi
Là phương pháp cơ bản nhất, có cách khai báo bằng cách chỉ rõ kích thước mảng và khởi tạo từng ký tự một.
char str[6] = {'H', 'e', 'l', 'l', 'o', '\0'};
Khi làm như vậy, str
có thể được xử lý như chuỗi "Hello"
. Điều quan trọng là luôn bao gồm '\0'
ở cuối.
Gán literal với kích thước chỉ định
Trong ngôn ngữ C, cũng có thể khởi tạo đơn giản bằng cách sử dụng literal chuỗi như sau.
char str[] = "Hello";
Trong trường hợp này, kích thước mảng sẽ tự động là "Hello"
+ '\0'
= 6 ký tự. Nếu muốn thay đổi nội dung chuỗi, việc khai báo dưới dạng mảng char sẽ cho phép thay đổi một cách an toàn.
Sử dụng cấp phát bộ nhớ động (malloc)
Có thể khởi tạo bằng literal chuỗi trong khi chỉ định kích thước mảng, nhưng cần chú ý tới việc thiếu kích thước.
char str[5] = "Hello"; // ❌ Nguyên nhân lỗi (kích thước không đủ)
Như trên, "Hello"
cần tổng cộng 6 ký tự: 5 ký tự + 1 ký tự (‘\0’). Do đó, ít nhất phải dùng char str[6]
.
4. Thao tác chuỗi
Nếu muốn xử lý chuỗi linh hoạt hơn, có cách sử dụng malloc
để cấp phát động một mảng char.
#include
#include
char* str = malloc(6 * sizeof(char));
strcpy(str, "Hello");
Phương pháp này cấp phát bộ nhớ động và sử dụng con trỏ để xử lý chuỗi. Sau khi sử dụng, đừng quên giải phóng bộ nhớ bằng free(str);
.
Sao chép chuỗi: strcpy
Trong C, việc sử dụng các hàm thư viện chuẩn để thao tác chuỗi bằng mảng char
là phổ biến. Ở đây, chúng tôi sẽ giải thích các hàm thao tác chuỗi cơ bản và cách dùng chúng kèm ví dụ cụ thể.
Nối chuỗi: strcat
strcpy
là một hàm sao chép một chuỗi vào một mảng char khác.
#include
char src[] = "Hello";
char dest[10];
strcpy(dest, src);
Lưu ý: dest
nếu không được cấp đủ kích thước sẽ gây tràn bộ đệm. Hãy đảm bảo kích thước bằng độ dài chuỗi cần sao chép + 1 (ký tự null).
Lấy độ dài chuỗi: strlen
strcat
là hàm nối hai chuỗi. Nội dung của đối số thứ hai được thêm vào cuối đối số thứ nhất.
#include
char str1[20] = "Hello";
char str2[] = " World";
strcat(str1, str2);
Kết quả, str1
sẽ là "Hello World"
. Cũng cần đảm bảo rằng mảng của đối số thứ nhất có đủ chỗ cho toàn bộ kích thước sau khi nối.
So sánh chuỗi: strcmp
strlen
trả về độ dài của chuỗi (số ký tự không tính ký tự null).
#include
char str[] = "Hello";
int len = strlen(str); // len = 5
Vì ký tự null không được tính, những người chưa quen với C cần lưu ý.
Tìm kiếm trong chuỗi: strchr
và strstr
Để so sánh hai chuỗi có giống nhau hay không, sử dụng strcmp
.
#include
char str1[] = "Hello";
char str2[] = "World";
if (strcmp(str1, str2) == 0) {
// bằng nhau
} else {
// khác nhau
}
Hàm này trả về 0 nếu bằng nhau, và trả về hiệu mã ký tự nếu khác nhau.
5. Sự khác nhau giữa mảng char và con trỏ
Để tìm ký tự hoặc chuỗi con cụ thể, có thể sử dụng các hàm sau.
#include
char str[] = "Hello World";
// Tìm ký tự
char *ptr1 = strchr(str, 'o'); // con trỏ tới ký tự 'o' đầu tiên
// Tìm chuỗi con
char *ptr2 = strstr(str, "World"); // con trỏ tới vị trí bắt đầu của "World"
Nếu không tìm thấy, cả hai hàm đều trả về NULL
.
Khác nhau trong khai báo
Trong ngôn ngữ C, khi làm việc với chuỗi, mảng char
và con trỏ char (char*)
có vẻ giống nhau, nhưng thực tế chúng có các tính chất khác nhau . Hiểu đúng sự khác biệt này sẽ giúp ngăn ngừa việc sử dụng bộ nhớ sai và các lỗi không mong muốn.
Khác nhau về khả năng ghi lại
Đầu tiên, hãy xem sự khác nhau trong cách khai báo.
char str1[] = "Hello"; // mảng char
char *str2 = "Hello"; // con trỏ char
str1
là mảng có thực thể và được cấp phát 6 byte trên bộ nhớ (“Hello” + ‘\0’). Ngược lại, str2
là con trỏ tới vùng bộ nhớ chứa literal chuỗi.
Khác nhau về cấu trúc bộ nhớ
Mảng char str1
cho phép thay đổi tự do các ký tự trong mảng.
str1[0] = 'h'; // OK
Tuy nhiên, khi truy cập literal chuỗi bằng con trỏ như char* str2 = "Hello";
, trong trường hợp truy cập chuỗi literal bằng con trỏ、việc thay đổi nội dung của nó là hành vi chưa định nghĩa。
str2[0] = 'h'; // ❌ hành vi chưa định nghĩa(có thể gây lỗi thời gian chạy)
Khác nhau trong việc lấy kích thước
mảng char
trên stackcon trỏ char
khoảng hằng (đọc chỉ) khoảng heap (malloc v.v.) cần chú ý đến cách xử lý bộ nhớ
Tóm tắt: Các điểm cần lưu ý khi sử dụng
Trong trường hợp mảng, sizeof(str1)
trả về tổng số byte của toàn bộ mảng.
char str1[] = "Hello";
printf("%lu", sizeof(str1)); // → 6(bao gồm '\0')
Ngược lại, với con trỏ, sizeof(str2)
trả về kích thước của chính con trỏ(thường 4-8 byte), không thể dùng để lấy kích thước mảng.
char *str2 = "Hello";
printf("%lu", sizeof(str2)); // → 8(môi trường 64bit)
6. Cách truyền mảng char vào hàm
Mục | mảng char | con trỏ char |
---|---|---|
Thay đổi nội dung | có thể | Nguyên tắc không được (trong trường hợp là literal) |
Lấy kích thước | sizeof() | strlen() |
Mục đích viết lại | phù hợp | Không phù hợp (chỉ đọc) |
độ linh hoạt | Kích thước cố định | Linh hoạt nhưng cần thận trọng |
Ví dụ cơ bản: truyền mảng char làm đối số
Trong ngôn ngữ C, khi truyền mảng vào hàm, không phải truyền giá trị mà là truyền con trỏ. Điều này cũng áp dụng cho mảng char
cũng tương tự, khi truyền vào hàm thì địa chỉ đầu của mảng(con trỏ)sẽ được truyền.
Việc hiểu cơ chế này rất quan trọng trong các trường hợp thao tác chuỗi hoặc thay đổi nội dung giữa các hàm.
Hàm thay đổi nội dung
#include
void printString(char str[]) {
printf("Chuỗi: %s
", str);
}
int main() {
char greeting[] = "Hello";
printString(greeting);
return 0;
}
Trong ví dụ này, printString
hàm nhận đối số kiểu char[]
nhưng thực tế nó được nhận như là char*
. Nói cách khác, char str[]
có cùng nghĩa với char *str
.
Quản lý kích thước mảng
Ngay cả khi thay đổi nội dung của mảng trong hàm, bạn có thể thao tác trực tiếp dữ liệu thông qua địa chỉ đã truyền.
#include
void toUpperCase(char str[]) {
for (int i = 0; str[i] != ' '; i++) {
if ('a' <= str[i] && str[i] <= 'z') {
str[i] = str[i] - ('a' - 'A');
}
}
}
int main() {
char text[] = "hello world";
toUpperCase(text);
printf("%s
", text); // Kết quả: HELLO WORLD
return 0;
}
Như vậy, ngay cả khi muốn thay đổi nội dung mảng, vì được xử lý như con trỏ, các thao tác trong hàm sẽ phản ánh lên phía gọi.
Đối số chỉ đọc sử dụng const
Trong ngôn ngữ C, khi truyền mảng vào hàm, thông tin kích thước không được truyền cùng. Do đó, để thao tác an toàn, nên truyền kích thước như một đối số, đây là thực hành tốt nhất.
void printChars(char str[], int size) {
for (int i = 0; i < size; i++) {
printf("%c ", str[i]);
}
printf("
");
}
Ngoài ra, việc sử dụng hàm strlen
để lấy độ dài chuỗi một cách động cũng phổ biến. Tuy nhiên, hãy chú ý không dùng nó cho các mảng không có kết thúc null.
7. Ví dụ thực hành: Hiển thị chuỗi theo thứ tự ngược
Nếu không thay đổi chuỗi trong hàm, nên sử dụng const char*
để chỉ ra rằng đối số chỉ đọc.
void printConstString(const char *str) {
printf("%s
", str);
}
Điều này giúp ngăn việc ghi đè không mong muốn và truyền đạt rõ ràng đặc tả của hàm.
Mục đích
Ở đây, hãy sử dụng kiến thức về mảng char
đã học cho đến nay để thực tế tạo chương trình hiển thị chuỗi theo thứ tự ngược.
Mã mẫu
Xuất từng ký tự một từ cuối chuỗi đến đầu chuỗi mà người dùng nhập. Đây là bài tập về thao tác mảng, lấy độ dài chuỗi và xử lý vòng lặp rất hiệu quả.
Giải thích
#include
#include
void printReversed(char str[]) {
int len = strlen(str);
for (int i = len - 1; i >= 0; i--) {
putchar(str[i]);
}
putchar('\n');
}
int main() {
char text[100];
printf("Vui lòng nhập chuỗi: ");
fgets(text, sizeof(text), stdin);
// Loại bỏ ký tự xuống dòng (biện pháp khi sử dụng fgets)
size_t len = strlen(text);
if (len > 0 && text[len - 1] == '\n') {
text[len - 1] = '\0';
}
printf("Hiển thị theo thứ tự ngược: ");
printReversed(text);
return 0;
}
Ví dụ thực thi
fgets
- Đừng quên thực hiện xử lý để loại bỏ (ký tự xuống dòng) được thêm vào cuối đầu vào。
- Sử dụng để lấy độ dài chuỗi và hiển thị từng ký tự theo thứ tự ngược lại.
Gợi ý ứng dụng
Vui lòng nhập chuỗi: OpenAI
Hiển thị theo thứ tự ngược: IAnepO
8. Các lỗi thường gặp và cách khắc phục
Quá trình này liên quan đến kiểm tra palindrome của chuỗi và hiểu cấu trúc ngăn xếp và có thể áp dụng khi học thuật toán. Ngoài ra, có thể viết lại bằng phép toán con trỏ, nên trở thành đề bài luyện tập nâng cao hơn.
1. Quên thêm ký tự kết thúc null(2. Thiếu kích thước bộ đệm
)
2. Thiếu kích thước bộ đệm
Khi làm việc với mảng char
trong ngôn ngữ C, có một số bẫy mà người mới bắt đầu đến người nâng cao đều dễ rơi vào. Ở đây, chúng tôi sẽ giải thích cụ thể các lỗi thường gặp và các biện pháp phòng ngừa, giải pháp.
3. Ghi đè literal chuỗi
Một trong những lỗi phổ biến nhất là quên thêm ký tự null(\0
)vào cuối chuỗi.
char str[5] = {'H', 'e', 'l', 'l', 'o'}; // ❌ Không có '\0' ở cuối
printf("%s\n", str); // Hành vi không xác định
Cách khắc phục:
char str[6] = {'H', 'e', 'l', 'l', 'o', '\0'}; // ✅ Đúng
Hoặc, việc sử dụng literal chuỗi sẽ tự động thêm ký tự null.
4. Quên xử lý ký tự xuống dòng của fgets
strcpy
và strcat
trong các hàm, nếu kích thước mảng đích không đủ, sẽ gây phá hủy bộ nhớ (tràn bộ đệm).
char str1[5];
strcpy(str1, "Hello"); // ❌ Sao chép 6 ký tự vào mảng kích thước 5
Cách khắc phục:
- Trước khi sao chép, làm (ví dụ: “số ký tự + 1”)
- Cũng nên xem xét việc sử dụng an toàn hơn
char str1[6];
strncpy(str1, "Hello", sizeof(str1) - 1);
str1[5] = '\0'; // Để chắc chắn, thêm ký tự null ở cuối
5. Nhầm lẫn giữa con trỏ và mảng
char *str = "Hello";
như được khai báo, có thể trỏ tới vùng không thể ghi. Nếu ghi vào đó, sẽ gây lỗi thời gian chạy.
char *str = "Hello";
str[0] = 'h'; // ❌ Segmentation fault tại thời gian chạy
Cách khắc phục:
char str[] = "Hello"; // ✅ Khai báo như mảng có thể ghi
str[0] = 'h';
9. Tóm tắt
fgets
được sử dụng để lấy chuỗi, cần chú ý rằng ký tự xuống dòng(\n
)sẽ còn lại ở cuối.
fgets(str, sizeof(str), stdin);
// str sẽ chứa nội dung như "Hello\n\0"
Cách khắc phục:
size_t len = strlen(str);
if (len > 0 && str[len - 1] == '\n') {
str[len - 1] = '\0';
}
Những gì đã học trong bài viết này
Do hình dạng tương tự, việc nhầm lẫn giữa char*
và char[]
có thể dẫn đến hành vi không xác định. Cần chú ý đến sự khác nhau trong việc lấy kích thước (sizeof
) và sự khác nhau về khả năng ghi.
Lời khuyên cho việc học trong tương lai
Trong bài viết này, chúng tôi đã giải thích về “mảng char trong ngôn ngữ C” từ cơ bản đến ứng dụng một cách từng bước. Cuối cùng, chúng tôi sẽ nhìn lại nội dung của bài viết và giới thiệu hướng dẫn học tập trong tương lai.
Những gì đã học trong bài viết này
- Vai trò và cách khai báo mảng char Trong ngôn ngữ C, vì không có kiểu chuỗi, để xử lý chuỗi ta sử dụng mảng char.
- Tầm quan trọng của chuỗi ký tự literal và ký tự null(
\0
) Để biểu diễn chuỗi, ký tự null ở cuối là điều không thể thiếu. - Xử lý chuỗi bằng hàm thư viện chuẩn , , , などを使えば、効率的に文字列を処理できます。
- Sự khác nhau giữa mảng char và con trỏ char Mảng và con trỏ giống nhau nhưng không giống nhau, có sự khác biệt rõ ràng về cấu trúc bộ nhớ và khả năng ghi đè.
- Cách truyền mảng vào hàm và lưu ý về tính an toàn Mảng thực sự được truyền dưới dạng con trỏ, vì vậy cần lưu ý khi ghi đè và quản lý kích thước.
- Thúc đẩy sự hiểu biết thông qua các ví dụ thực hành và lỗi thường gặp Bằng cách áp dụng trong mã thực tế và cách xử lý lỗi, chúng tôi đã học cách sử dụng thực tế hơn.
Lời khuyên cho việc học trong tương lai
Hiểu cách sử dụng mảng char là bước đầu tiên để nắm bắt nền tảng thao tác bộ nhớ trong ngôn ngữ C cơ bản . Kiến thức đã học ở đây có thể được áp dụng vào các bước tiếp theo như sau.
- Phép toán con trỏ
- Quản lý bộ nhớ động
malloc
free
- Kết hợp với cấu trúc dữ liệu
Ngoài ra, việc đọc mã do người khác viết giúp bạn tiếp thu những góc nhìn và cách viết mà bản thân chưa có. Thói quen đọc các dự án thực tế và OSS(phần mềm nguồn mở)cũng rất hiệu quả.
Ngôn ngữ C có độ tự do cao, vì vậy nếu không xử lý đúng sẽ có những rủi ro, nhưng nếu bạn xây dựng nền tảng một cách cẩn thận, nó sẽ trở thành một công cụ rất mạnh. Hiểu mảng char là bước đầu tiên. Hãy tham khảo bài viết này và nâng cao kỹ năng một cách chắc chắn.