1. Giới thiệu
Ngôn ngữ C vẫn được sử dụng rộng rãi trong các lĩnh vực lập trình hệ thống và lập trình nhúng. Trong ngôn ngữ này, chuỗi ký tự và mảng là những yếu tố quan trọng để quản lý dữ liệu. Khi học C, việc hiểu rõ đặc điểm riêng là chuỗi được xử lý như “mảng ký tự” là điều không thể bỏ qua.
Bài viết này sẽ đi sâu vào các khái niệm cơ bản về chuỗi và mảng trong C, giúp người học từ trình độ cơ bản đến trung cấp giải đáp thắc mắc về “sự khác nhau và mối liên hệ giữa chuỗi và mảng”.
Thông qua các ví dụ chương trình thực tế, chúng ta cũng sẽ tìm hiểu cách khai báo mảng và chuỗi, các hàm cơ bản để xử lý chuỗi, cũng như những lưu ý về quản lý bộ nhớ. Điều này sẽ giúp bạn thao tác với chuỗi trong C an toàn và hiệu quả hơn.
2. Kiến thức cơ bản về mảng
Việc hiểu mảng trong C là nền tảng để thao tác chuỗi. Phần này sẽ giải thích khái niệm và cách sử dụng mảng.
Mảng là gì?
Mảng là cấu trúc lưu trữ các dữ liệu cùng kiểu trong một vùng nhớ liên tiếp. Ví dụ, khi khai báo mảng kiểu int
, bạn có thể xử lý nhiều số nguyên cùng lúc. Trong C, mảng được khai báo như sau:
int numbers[5]; // Mảng lưu 5 số nguyên
Dòng lệnh trên khai báo mảng số nguyên numbers
và cấp phát vùng nhớ cho 5 số nguyên. Để truy cập từng phần tử, bạn sử dụng chỉ số (index).
Khai báo và khởi tạo mảng
Bạn có thể khởi tạo giá trị ngay khi khai báo mảng. Khởi tạo nghĩa là gán giá trị ban đầu tại thời điểm khai báo.
int numbers[5] = {1, 2, 3, 4, 5}; // Khai báo và khởi tạo mảng
Ở đây, mảng numbers
được gán lần lượt các giá trị từ 1 đến 5. Nếu bỏ qua phần khởi tạo, các phần tử sẽ có giá trị không xác định (dữ liệu rác trong bộ nhớ).
Bố trí bộ nhớ và cách truy cập mảng
Trong C, các phần tử của mảng được lưu liên tiếp trong bộ nhớ. Ví dụ, khi khai báo int numbers[5]
, các phần tử numbers[0]
đến numbers[4]
sẽ nằm liền nhau trong bộ nhớ.
Chỉ số mảng bắt đầu từ 0 và kết thúc ở (kích thước mảng – 1).
printf("%d", numbers[0]); // In ra phần tử đầu tiên
Nhờ mảng, bạn có thể quản lý nhiều dữ liệu cùng kiểu trong một biến và thao tác hiệu quả hơn.
3. Kiến thức cơ bản về chuỗi
Trong C, chuỗi không chỉ đơn thuần là dãy ký tự mà được xử lý như một mảng đặc biệt. Phần này sẽ giới thiệu cấu trúc và cách thao tác với chuỗi trong C.
Chuỗi là gì?
Trong C, chuỗi được biểu diễn dưới dạng mảng ký tự và kết thúc bằng ký tự null ('\0'
). Ký tự null này đóng vai trò đánh dấu điểm kết thúc của chuỗi, rất quan trọng khi thao tác với chuỗi.
Ví dụ khai báo chuỗi như sau:
char greeting[6] = {'H', 'e', 'l', 'l', 'o', '\0'};
Ở đây, mảng greeting
chứa 5 ký tự “Hello” và ký tự null ở cuối. Trong C, chuỗi luôn được kết thúc bằng '\0'
để trình biên dịch biết điểm dừng.
Khai báo và khởi tạo chuỗi
Bạn có thể khởi tạo chuỗi trực tiếp bằng cách dùng chuỗi ký tự (string literal):
char greeting[] = "Hello";
Khi dùng cách này, trình biên dịch sẽ tự động thêm ký tự '\0'
vào cuối, nên mảng sẽ có kích thước 6 (5 ký tự + 1 ký tự null). Nếu không lưu ý ký tự null, có thể dẫn đến lỗi khi xử lý chuỗi.
Sự khác nhau giữa chuỗi literal và mảng ký tự
Chuỗi literal được khai báo với kiểu const char*
và không thể thay đổi nội dung:
const char *greeting = "Hello"; // Chuỗi literal
Ngược lại, mảng ký tự có thể thay đổi từng phần tử:
char greeting[] = "Hello";
greeting[0] = 'h'; // Thay đổi "Hello" thành "hello"
Hiểu rõ sự khác biệt này giúp bạn quản lý bộ nhớ tốt hơn và tránh lỗi.
4. Mối quan hệ giữa chuỗi và mảng
Trong C, chuỗi được cài đặt dưới dạng “mảng ký tự”. Phần này sẽ giải thích ý nghĩa và cách khai báo, khởi tạo chuỗi.
Chuỗi là mảng ký tự
Bạn có thể khai báo mảng kiểu char
để chứa chuỗi, ví dụ:
char name[10] = "Alice";
Mảng này sẽ chứa các ký tự {'A', 'l', 'i', 'c', 'e', '\0', '\0', '\0', '\0', '\0'}
. Những phần tử dư sẽ chứa ký tự null.
Lưu ý khi khai báo chuỗi
Kích thước mảng phải đủ để chứa toàn bộ ký tự của chuỗi cộng thêm ký tự null. Nếu quá nhỏ, sẽ gây lỗi hoặc hành vi không mong muốn:
char name[3] = "Alice"; // Lỗi vì không đủ bộ nhớ
Gán và thao tác với chuỗi
Không thể gán toàn bộ mảng trực tiếp. Muốn thay đổi chuỗi, cần thay đổi từng phần tử hoặc dùng hàm thư viện:
name[0] = 'B'; // Thay đổi ký tự đầu
strcpy(name, "Bob"); // Sao chép toàn bộ chuỗi
Khởi tạo chuỗi
Có thể khởi tạo chuỗi khi khai báo, hoặc để trình biên dịch tự xác định kích thước:
char greeting[] = "Hello";
Nếu chỉ định kích thước, hãy chắc chắn đủ chỗ cho ký tự null.

5. Các hàm cơ bản xử lý chuỗi
Ngôn ngữ C cung cấp nhiều hàm trong thư viện chuẩn để thao tác chuỗi một cách hiệu quả. Phần này sẽ giới thiệu các hàm sao chép, nối, lấy độ dài và so sánh chuỗi.
Hàm sao chép chuỗi strcpy
strcpy
sao chép nội dung của một chuỗi sang chuỗi khác, giúp bạn không phải gán từng ký tự một.
#include <string.h>
char source[] = "Hello";
char destination[10];
strcpy(destination, source); // Sao chép source vào destination
Hãy đảm bảo mảng đích (destination
) đủ lớn để chứa toàn bộ chuỗi nguồn và ký tự null, tránh tràn bộ đệm (buffer overflow).
Hàm nối chuỗi strcat
strcat
nối chuỗi thứ hai vào cuối chuỗi thứ nhất.
#include <string.h>
char greeting[20] = "Hello";
char name[] = " World";
strcat(greeting, name); // greeting trở thành "Hello World"
Chuỗi đích phải đủ dung lượng để chứa cả hai chuỗi sau khi nối và ký tự null.
Lấy độ dài chuỗi với strlen
strlen
trả về số lượng ký tự trong chuỗi (không tính ký tự null).
#include <string.h>
char greeting[] = "Hello";
int length = strlen(greeting); // length = 5
So sánh chuỗi với strcmp
strcmp
so sánh hai chuỗi và trả về 0 nếu giống nhau, giá trị dương hoặc âm nếu khác nhau.
#include <string.h>
char str1[] = "Hello";
char str2[] = "Hello";
char str3[] = "World";
int result1 = strcmp(str1, str2); // 0 (giống nhau)
int result2 = strcmp(str1, str3); // khác 0 (khác nhau)
6. Mảng chuỗi (Mảng 2 chiều)
Khi làm việc với nhiều chuỗi, bạn có thể dùng mảng 2 chiều để quản lý tất cả trong một cấu trúc duy nhất.
Cơ bản về mảng 2 chiều
char names[3][10] = {
"Alice",
"Bob",
"Carol"
};
Mỗi hàng trong mảng là một chuỗi, có thể chứa tối đa 9 ký tự + ký tự null.
Truy cập mảng 2 chiều
char first_char = names[0][0]; // 'A'
for (int i = 0; i < 3; i++) {
printf("%s\n", names[i]);
}
Khởi tạo và lưu ý kích thước
char colors[3][10] = {"Red", "Green", "Blue"};
Nếu chuỗi dài hơn kích thước đã khai báo, sẽ gây tràn bộ nhớ. Khi không chắc độ dài, hãy cân nhắc cấp phát động.
Ví dụ sắp xếp chuỗi với mảng 2 chiều
#include <stdio.h>
#include <string.h>
int main() {
char names[3][10] = {"Bob", "Alice", "Carol"};
char temp[10];
for (int i = 0; i < 2; i++) {
for (int j = i + 1; j < 3; j++) {
if (strcmp(names[i], names[j]) > 0) {
strcpy(temp, names[i]);
strcpy(names[i], names[j]);
strcpy(names[j], temp);
}
}
}
for (int i = 0; i < 3; i++) {
printf("%s\n", names[i]);
}
return 0;
}
7. Quản lý bộ nhớ và lưu ý
Quản lý bộ nhớ là yếu tố cực kỳ quan trọng khi thao tác với mảng và chuỗi trong C. Sai sót có thể dẫn tới tràn bộ đệm hoặc rò rỉ bộ nhớ.
Tràn bộ đệm (Buffer overflow)
char buffer[10];
strcpy(buffer, "This is a very long string"); // Lỗi tràn bộ đệm
Sử dụng strncpy
để an toàn hơn
char buffer[10];
strncpy(buffer, "This is a very long string", sizeof(buffer) - 1);
buffer[sizeof(buffer) - 1] = '\0';
Cấp phát động với malloc
#include <stdlib.h>
#include <string.h>
char *str = (char *)malloc(20 * sizeof(char));
strcpy(str, "Dynamic allocation");
free(str);
Tránh rò rỉ bộ nhớ
char *name = (char *)malloc(50 * sizeof(char));
strcpy(name, "John Doe");
free(name);
8. Tổng kết
Bài viết đã trình bày từ kiến thức cơ bản về mảng và chuỗi trong C, các hàm chuẩn để thao tác chuỗi, cách sử dụng mảng 2 chiều cho nhiều chuỗi, cho tới quản lý bộ nhớ và phòng tránh lỗi tràn bộ đệm.
Hiểu cơ bản về mảng và chuỗi
Chuỗi trong C là mảng ký tự kết thúc bằng '\0'
. Mảng giúp lưu trữ và quản lý nhiều dữ liệu cùng kiểu một cách hiệu quả.
Các hàm chuẩn xử lý chuỗi
Các hàm như strcpy
, strcat
, strlen
, strcmp
giúp thao tác chuỗi nhanh chóng, nhưng cần sử dụng đúng cách để tránh lỗi.
Quản lý nhiều chuỗi với mảng 2 chiều
Mảng 2 chiều rất hữu ích khi lưu danh sách tên, từ vựng… nhưng cần xác định kích thước hợp lý.
Quản lý bộ nhớ và phòng tránh lỗi
Luôn chú ý đến kích thước bộ đệm, ký tự null, và giải phóng bộ nhớ sau khi sử dụng để tránh rò rỉ và lỗi bảo mật.
Lời kết
Làm chủ thao tác với chuỗi và mảng trong C đòi hỏi luyện tập và hiểu rõ nguyên lý. Áp dụng đúng kiến thức sẽ giúp bạn lập trình an toàn và hiệu quả hơn.