Mở đầu
Trước khi bước vào thế giới lập trình, hãy cùng chiêm ngưỡng vẻ đẹp của những vần thơ và cảnh sắc hữu tình.
Phần 1: Quản lý Bộ nhớ Thủ công cho Chuỗi Ký tự trong C
1. C không có kiểu dữ liệu chuỗi
"Xin chào thế giới.\n"
Chuỗi ký tự được đặt trong dấu ngoặc kép (Double Quote) được gọi là hằng chuỗi (String Literal) hoặc đơn giản là chuỗi. Lưu ý: Ký tự kết thúc của một chuỗi là \0. Khi tính độ dài chuỗi, \0 được coi là dấu kết thúc và không được tính vào nội dung chuỗi.
2. Tầm quan trọng của '\0'
#include <stdio.h>
//Kết quả in ra của đoạn code dưới đây là gì? Tại sao?
int main()
{
char mang1[] = "Xin chao";
char mang2[] = { 'X', 'i', 'n', ' ', 'c', 'h', 'a', 'o' };
char mang3[] = { 'X', 'i', 'n', ' ', 'c', 'h', 'a', 'o', '\0'};
printf("%s\n", mang1);
printf("%s\n", mang2);
printf("%s\n", mang3);
return 0;
}
Như bạn có thể thấy từ kết quả chạy mã ở trên, khi xuất ra mảng mang2 đã xuất hiện hiện tượng mã lỗi
Đặt câu hỏi: Tại sao lại xảy ra tình huống này? Bởi vì trong mảng mang2 không chứa ký tự kết thúc chuỗi tức là '\0', nên đã xảy ra hiện tượng mã lỗi như trên!
Phân tích sâu về Chuỗi và \0
- Khi chúng ta sử dụng hàm printf() để in ra chuỗi, chương trình sẽ chỉ dừng lại khi gặp ký tự '\0'
Chắc chắn bạn sẽ hỏi: Tại sao khi in mảng mang1 lại không xảy ra hiện tượng mã lỗi? 2. Bởi vì trong chuỗi được đặt trong dấu ngoặc kép "" đã tự động chứa ký tự kết thúc chuỗi là '\0', do đó khi in mảng mang1 tự nhiên sẽ không xảy ra hiện tượng mã lỗi 3. Khi chúng ta sử dụng cách định nghĩa chuỗi bằng dấu ngoặc nhọn {}, chúng ta cần phải thêm ký tự kết thúc '\0', nếu không sẽ xảy ra hiện tượng mã lỗi
Lưu ý: Một lập trình viên giỏi cần có tố chất kiểm soát chặt chẽ từng chi tiết trong mã, dù đôi khi chúng ta không cần sử đến, nhưng đây là điều bắt buộc chúng ta phải làm.
3. Cách khởi tạo chuỗi
| Phương pháp | Ví dụ | Giải thích |
|---|---|---|
| **Gán trực tiếp** | char chuoi\[\] = "abc"; |
Tự tính độ dài và thêm '\\0' (độ dài=4) |
| **Chỉ định kích thước** | char chuoi\[5\] = "abc"; |
Các vị trí còn lại được lấp đầy bằng '\\0' (thực tế lưu a b c \\0 \\0) |
| **Ký tự từng cái** | char chuoi\[4\] = {'a','b','c','\\0'}; |
Phải thêm '\\0' một cách tường minh, nếu không không phải là chuỗi hợp lệ |
| **Con trỏ** | char \*chuoi = "abc"; |
Trỏ đến hằng chuỗi chỉ đọc (không thể sửa đổi nội dung) |
4. Định dạng chuỗi trong C
Trong ngôn ngữ C, các định dạng chuỗi chủ yếu được sử dụng trong các hàm nhập/xuất
(1)%s
Mục đích: Nhập/xuất chuỗi (mảng char hoặc con trỏ kết thúc bằng '\0').
Ví dụ mã:
char str[] = "Xin chao";
printf("%s\n", str); // Xuất chuỗi
scanf("%s", str); // Nhập chuỗi (dừng khi gặp khoảng trắng)
- Lưu ý: scanf("%s", str) không an toàn (có thể gây tràn bộ đệm), nên sử dụng:
scanf("%19s", str); // Hạn chế độ dài tối đa (kích thước mảng-1)
- Hoặc sử dụng fgets:
fgets(str, sizeof(str), stdin); // An toàn hơn (đọc một dòng)
(2)%c
Mục đích: Nhập/xuất ký tự đơn (kiểu char).
Ví dụ mã:
char ch = 'A';
printf("%c\n", ch); // Xuất ký tự 'A'
scanf("%c", &ch); // Nhập ký tự đơn (bao gồm khoảng trắng/xuống dòng)
Lưu ý:
Nếu muốn bỏ qua các ký tự trắng, thêm khoảng trắng:
scanf(" %c", &ch); // Bỏ qua khoảng trắng/xuống dòng ở phía trước
(3)%p
Mục đích: Xuất địa chỉ của con trỏ (dạng thập lục phân).
Ví dụ mã:
char *ptr = "Xin chao";
printf("%p\n", (void *)ptr); // Xuất địa chỉ con trỏ (ví dụ: 0x55a1b2c3d4e5)
Khi chúng ta tìm hiểu về con trỏ ở phần sau, chúng ta sẽ phân tích sâu hơn
Mở rộng: fgets
fgets là một hàm quan trọng trong thư viện chuẩn C để đọc chuỗi một cách an toàn, so với gets và scanf %s, nó cung cấp khả năng kiểm soát tốt hơn và an toàn hơn.
Mẫu sử dụng fgets đơn giản nhất
#include <stdio.h>
int main() {
char buffer[100]; // Định nghĩa mảng ký tự đủ lớn
printf("Vui lòng nhập nội dung: ");
fgets(buffer, sizeof(buffer), stdin); // Đọc đầu vào
printf("Bạn đã nhập: %s", buffer);
return 0;
}z
Điểm chính cần nhớ
Ba tham số cần thiết: buffer: Mảng ký tự lưu trữ đầu vào sizeof(buffer): Độ dài đọc tối đa (tự tính kích thước mảng) stdin: Đọc từ đầu vào tiêu chuẩn (bàn phím)
Tự động thêm ký tự kết thúc: fgets sẽ tự động thêm \0 ở cuối chuỗi Do đó số ký tự thực tế đọc được = độ dài chỉ định - 1
Mở rộng: Cách bỏ ký tự xuống dòng khi sử dụng fgets
Nếu người dùng nhấn Enter, ký tự xuống dòng \n sẽ được bao gồm trong chuỗi Ví dụ: Sau khi nhập "xin chao" và nhấn Enter, nội dung buffer sẽ là "xin chao\n\0"
Lúc này chúng ta có thể sử dụng thư viện <string.h> để loại bỏ ký tự xuống dòng *Giải thích về thư viện <string.h> sẽ được trình bày ở phần dưới"
#include <stdio.h>
#include <string.h> // Cần sử dụng strcspn
int main() {
char buffer[100];
printf("Vui lòng nhập: ");
fgets(buffer, sizeof(buffer), stdin);
// Loại bỏ ký tự xuống dòng
buffer[strcspn(buffer, "\n")] = '\0';
printf("Đầu vào đã xử lý: %s", buffer);
return 0;
}
(4)Các định dạng liên quan khác
Dưới đây là các định dạng này có chức năng mạnh mẽ,nhưng ít khi được sử dụng, chúng tôi chỉ giải thích đơn giản ở đây, những bạn quan tâm có thể tìm hiểu thêm
1. %[…] (Tập quét)
Mục đích: Tùy chỉnh quy tắc nhập của scanf.
- Ví dụ mã:
char str[50];
scanf("%[^\n]", str); // Đọc một dòng (đến khi gặp ký tự xuống dòng)
scanf("%[a-z]", str); // Chỉ đọc các chữ cái thường
- Cách sử dụng phổ biến:
%[^\n]: Đọc một dòng (bao gồm khoảng trắng). %[^,]: Đọc đến khi gặp dấu phẩy.
2. %n
Mục đích: Ghi số ký tự đã xuất (không tiêu thụ tham số).
- Ví dụ mã:
int count;
printf("Hello%nWorld\n", &count); // count = 5 (độ dài của "Hello")
3. %*s (Ngăn gán)
Mục đích: Bỏ qua chuỗi nhập vào.
- Ví dụ mã:
char str[10];
scanf("%*s %s", str); // Bỏ qua từ đầu tiên, đọc từ thứ hai
Phần 2: Các Hàm Thư viện Chuỗi trong C
<string.h> là thư viện quan trọng trong C để xử lý chuỗi và thao tác bộ nhớ, dưới đây giới thiệu các h常用的 nhất, phù hợp cho người mới bắt đầu nắm bắt các chức năng cốt lõi.
Vì <string.h> chứa nhiều thuật ngữ tiếng Anh, để giúp mọi người dễ nhớ, tôi sẽ đặt thuật ngữ tiếng Anh ở phía trước và giải thích chức năng của nó ở phía sau, giúp mọi người ghi nhớ dễ dàng hơn"
1. Các thao tác cơ bản trên chuỗi
(1) strlen (Tính độ dài)
size_t len = strlen(str); // Không tính '\0'
(2) strcpy (Sao chép)
strcpy(char dest[] , const char nguon[]); // Có thể gây tràn
- Chức năng: Sao chép chuỗi nguồn vào chuỗi đích
(2.1)strncpy (Sao chép):
strncpy(dest, nguon, n); // Sao chép tối đa n ký tự
- Chức năng: strncpy có thể chỉ định số ký tự sao chép tối đa
(3) strcat (Nối chuỗi)
strcat(dest, nguon); // Có thể gây tràn
(3.1)strncat (Nối chuỗi)
strncat(dest, nguon, n); // Nối tối đa n ký tự
(4) strcmp (So sánh)
int strcmp(const char str1[], const char str2[]);
- Chức năng: So sánh hai chuỗi
(4.1)strncmp (So sánh)
int strncmp(const char str1[], const char str2[], size_t n);
- Chức năng: So sánh n ký tự đầu tiên của hai chuỗi
Giá trị trả về:
- 0: Hai chuỗi bằng nhau
- Số âm: str1 nhỏ hơn str2
- Số dương: str1 lớn hơn str2
Ví dụ mã:
if(strcmp("tao", "chuoi") < 0) {
printf("tao đứng trước chuoi");
}
2. Các hàm thao tác bộ nhớ
(1)memset (Đặt bộ nhớ)
void memset(char arr[], int value, size_t num);
- Chức năng: Đặt num byte đầu tiên của mảng arr thành value, thường dùng để khởi tạo mảng
Ví dụ mã:
char buffer[50];
memset(buffer, 0, 50); // Khởi tạo toàn bộ buffer bằng 0
(2)memcpy (Sao chép bộ nhớ)
void memcpy(char dest[], const char nguon[], size_t num);
- Chức năng: Sao chép num byte từ mảng nguồn sang mảng đích
Ví dụ mã:
int nguon[5] = {1,2,3,4,5};
int dest[5];
memcpy(dest, nguon, sizeof(nguon)); // Sao chép toàn bộ mảng
3. Các hàm tìm kiếm trong bộ nhớ
(1) strchr (Tìm vị trí ký tự)
int strchr(const char str[], int c);
- Chức năng: Tìm vị trí xuất hiện đầu tiên của ký tự c trong str, trả về chỉ số của ký tự, không tìm thấy trả về -1
Ví dụ mã:
int pos = strchr("Xin chao", 'c'); // pos bây giờ là 4
(2) strstr (Tìm vị trí chuỗi con)
int strstr(const char haystack[], const char needle[]);
- Chức năng: Tìm chuỗi needle trong haystack, trả về chỉ số bắt đầu của chuỗi con, không tìm thấy trả về -1
Ví dụ mã:
int pos = strstr("Xin chao the gioi", "the"); // pos bây giờ là 8
Lưu ý khi sử dụng Khi sử dụng các hàm này, hãy đảm bảo mảng đích có đủ kích thước Chuỗi phải kết thúc bằng '\0' Chỉ số mảng bắt đầu từ 0 Nhớ bao gồm thư viện #include <string.h>
Lời kết
Để giúp mọi người dễ hiểu, chuỗi trong phần này chưa có nội dung về con trỏ, khi nói về con trỏ ở phần sau, chúng ta sẽ kết nối với chuỗi để giải thích
Nếu thấy hữu ích, hãy cho chúng tôi một like, share và comment nhé!