strlen
Sau bài này bạn sẽ làm được
- Phân biệt strlen với sizeof
- Hiểu vì sao strlen tốn thời gian tỷ lệ với độ dài chuỗi
- Tránh gọi strlen lặp lại trong điều kiện vòng lặp
- Tự cài lại strlen bằng hai cách
Hàm strlen đơn giản tới mức cài lại chỉ mất bốn dòng. Nhưng đằng sau nó là một đặc điểm quan trọng của chuỗi C mà nếu bỏ qua, bạn sẽ vô tình viết ra những vòng lặp chậm gấp hàng nghìn lần cần thiết.
#Cách dùng
#include <string.h>
size_t strlen(const char *s);Trả về số ký tự trước byte 0. Byte 0 không được tính vào. Chuỗi rỗng có độ dài 0.
#include <stdio.h>
#include <string.h>
int main(void)
{
printf("%zu\n", strlen("hello")); /* 5 */
printf("%zu\n", strlen("")); /* 0 */
printf("%zu\n", strlen("a b c")); /* 5, dấu cách cũng là ký tự */
printf("%zu\n", strlen("dong\n")); /* 5, ký tự xuống dòng cũng tính */
return 0;
}#strlen so với sizeof
| strlen | sizeof | |
|---|---|---|
| Là gì | Lời gọi hàm | Toán tử của ngôn ngữ |
| Tính lúc nào | Lúc chạy | Lúc biên dịch, trừ mảng độ dài thay đổi |
| Đếm cái gì | Ký tự trước byte 0 | Số byte của toàn bộ đối tượng |
| Byte kết thúc | Không tính | Có tính |
| Chi phí | Tỷ lệ với độ dài chuỗi | Bằng không |
| Dùng với con trỏ | Được, đếm nội dung | Cho ra 8, kích thước con trỏ |
char a[10] = "hello";
char *p = "hello";
strlen(a); /* 5 */
sizeof a; /* 10, kích thước mảng khai báo */
strlen(p); /* 5 */
sizeof p; /* 8, kích thước con trỏ */
sizeof "hello"; /* 6, hằng chuỗi là mảng 6 ký tự */#Chi phí tuyến tính
Vì chuỗi C không lưu độ dài, strlen phải đi từng byte cho tới khi gặp byte 0. Với chuỗi một triệu ký tự, nó đọc một triệu byte.
chuoi 10 ky tu, 1 000 000 lan goi: 0.002 s chuoi 1 000 000 ky tu, 1 000 000 lan goi: uoc tinh 20 phut
#Bẫy gọi strlen trong điều kiện vòng lặp
for (size_t i = 0; i < strlen(s); ++i)
if (s[i] == 'a') ++dem;
/* Với chuỗi n ký tự, đây là n lời gọi strlen, mỗi lời gọi đọc n byte.
Tổng cộng n² phép đọc. Với n = 100000 là mười tỷ phép đọc. */size_t n = strlen(s);
for (size_t i = 0; i < n; ++i)
if (s[i] == 'a') ++dem;
/* n phép đọc cho strlen, cộng n phép đọc cho vòng lặp. Tổng cộng 2n. */goi strlen moi luot : 4.318 s goi mot lan : 0.000 s
#Tự cài lại
/* Cách 1: bằng chỉ số */
size_t my_strlen1(const char *s)
{
size_t n = 0;
while (s[n] != '\0')
++n;
return n;
}
/* Cách 2: bằng con trỏ, lấy hiệu hai con trỏ */
size_t my_strlen2(const char *s)
{
const char *p = s;
while (*p != '\0')
++p;
return (size_t)(p - s);
}Cách thứ hai là cách của thư viện chuẩn. Phép trừ hai con trỏ cùng kiểu cho ra số phần tử giữa chúng, kiểu ptrdiff_t. Chương 12 sẽ dạy kỹ về số học con trỏ.
Bộ kiểm thử
#include <assert.h>
#include <string.h>
static void kiem_thu(const char *s)
{
assert(my_strlen1(s) == strlen(s));
assert(my_strlen2(s) == strlen(s));
}
int main(void)
{
kiem_thu(""); /* chuỗi rỗng */
kiem_thu("a"); /* một ký tự */
kiem_thu("hello"); /* bình thường */
kiem_thu("a b\tc\n"); /* có khoảng trắng */
kiem_thu("\x01\x02\x03"); /* ký tự không in được */
char dai[10001];
memset(dai, 'x', 10000);
dai[10000] = '\0';
kiem_thu(dai); /* chuỗi dài */
puts("Tat ca kiem thu deu qua");
return 0;
}Thủ thuật đọc theo khối
/* Ý tưởng của bản trong thư viện: kiểm tra tám byte cùng lúc.
Biểu thức (x - 0x0101...) & ~x & 0x8080... khác 0 khi và chỉ khi
trong tám byte của x có ít nhất một byte bằng 0. */
static int co_byte_khong(unsigned long long x)
{
return ((x - 0x0101010101010101ULL) & ~x & 0x8080808080808080ULL) != 0;
}Bạn chưa cần hiểu biểu thức này ngay, Chương 31 về thao tác bit sẽ giải thích. Điều đáng ghi nhận bây giờ là: cùng một bài toán, cùng một độ phức tạp, mà cách cài đặt tạo ra chênh lệch mười lần. Đó là lý do luôn nên dùng hàm thư viện thay vì bản tự viết, trừ khi bạn đang học.
Tự làm thử
- In
strlenvàsizeofcủa một mảng ký tự, một con trỏ và một hằng chuỗi. Giải thích cả sáu con số. - Viết vòng lặp gọi
strlenmỗi lượt trên chuỗi hai trăm nghìn ký tự, đo thời gian, rồi sửa và đo lại. - Cài cả hai phiên bản
my_strlenvà chạy bộ kiểm thử so với bản chuẩn. - Đo thời gian của bản tự cài và bản thư viện trên chuỗi mười triệu ký tự, tính tỷ lệ.
- Viết vòng lặp duyệt ngược chuỗi bằng cả hai cách trong bài, thử với chuỗi rỗng.
- Viết macro
BAT_DAU_BANGvà dùng nó để lọc các dòng bắt đầu bằng một tiền tố cho trước.
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
strlenđếm ký tự trước byte 0 và không tính byte đó, trả về kiểusize_tkhông dấu.sizeoftính lúc biên dịch và đếm cả byte kết thúc.strlentính lúc chạy và không đếm.- Chi phí của strlen tỷ lệ với độ dài chuỗi, vì chuỗi C không lưu độ dài ở đâu cả.
- Không bao giờ gọi
strlentrong điều kiện vòng lặp. Gọi một lần hoặc dùng điều kiệns[i] != '\0'. - Kiểm thử hàm tự cài bằng cách so kết quả với hàm chuẩn trên nhiều trường hợp biên.