Skip to content

C 字符串

在 C 语言中,字符串(string)不是像 int 或 float 那样内建的基本类型(built-in fundamental type)。相反,字符串通常被表示为一个字符数组(array of characters),并由一个特殊的空字符(null character),即 ‘\0’(其 ASCII 值为 0)终止。

这通常被称为“空终止字符串”(“null-terminated string”)或“C 风格字符串”(“C-style string”)。空字符(null character)标志着字符串数据在数组中的结束位置。

有几种方法可以声明和初始化字符串:

**1. 使用字符串字面量(String Literal):**这是最常见的方法。编译器会自动为字符内容加上空终止符(null terminator)分配足够的空间。

char message[] = "Hello"; // Compiler allocates 6 chars ('H','e','l','l','o','\0')
char *str_ptr = "World"; // Creates a string literal (often in read-only memory),
// str_ptr points to its first character.

**警告(Warning):**尝试修改由指针(pointer)指向的字符串字面量(如 str_ptr[0] = ‘J’;)会导致未定义行为(undefined behavior)(通常是程序崩溃)。

**2. 指定大小并使用字符串字面量:**确保指定的大小足够大,能容纳字符串内容以及空终止符。

char greeting[10] = "Hi"; // Allocates 10 chars. Initialized to 'H','i','\0'.
// Remaining 7 chars are initialized to '\0'.

3. 使用字符初始化列表:如果使用此方法,你必须明确包含空终止符。

char word[4] = {'b', 'y', 'e', '\0'}; // Correctly null-terminated
char bad_word[3] = {'o', 'o', 'p'}; // NOT a valid C string! Missing '\0'.

标准输入/输出库(<stdio.h>)提供了用于处理字符串的函数:

  • **printf 函数配合 %s:**打印一个空终止字符串(null-terminated string)。
  • **scanf 函数配合 %s:**读取一串非空白字符到一个字符数组中。**警告:**极其不安全,因为它不检查缓冲区边界(buffer boundaries)。容易导致缓冲区溢出(buffer overflows)。避免不指定宽度限制地使用 scanf(“%s”, …)。
  • **fgets 函数:**从流(stream)(如 stdin)中读取一行文本(包括空格,直到换行符或达到指定限制)到一个字符数组中。比 scanf 安全得多,因为它将缓冲区大小(buffer size)作为参数。
  • **puts 函数:**打印一个字符串后跟一个换行符。

使用 fgets 和 printf 的示例:

#include <stdio.h>
#include <string.h> // For strcspn
#define BUFFER_SIZE 50
int main(void) {
char name[BUFFER_SIZE];
printf("Enter your name: ");
// Safely read input using fgets
if (fgets(name, BUFFER_SIZE, stdin) != NULL) {
// Remove trailing newline character if present (fgets includes it)
name[strcspn(name, "\n")] = 0;
printf("Hello, %s!\n", name);
} else {
printf("Error reading input.\n");
}
return 0;
}

常用字符串操作函数(<string.h>)

Section titled “常用字符串操作函数(<string.h>)”

C 标准库在 <string.h> 中提供了一组用于常用字符串操作的函数。对不接受缓冲区大小参数的函数要非常小心,因为如果目标缓冲区不够大,它们很容易导致缓冲区溢出。

函数(Function)描述(Description)安全注意事项(Safety Considerations)
strlen(s)返回字符串 s 的长度(不包括空终止符)。如果 s 正确空终止,通常是安全的。
strcpy(dest, src)将字符串 src(包括 \0)复制到 dest 中。**不安全(UNSAFE)。**不检查 dest 是否足够大。容易导致缓冲区溢出。避免使用。
strncpy(dest, src, n)最多复制 n 个字符从 src 到 dest。比 strcpy 安全,但有陷阱:如果 src 的长度 >= n,可能不会在 dest 中添加空终止符。总是手动添加空终止符:如果大小是 n,使用 dest[n-1] = ‘\0’;。
strcat(dest, src)将字符串 src 追加到字符串 dest 的末尾。**不安全(UNSAFE)。**不检查 dest 是否有足够的空间容纳合并后的字符串 + \0。避免使用。
strncat(dest, src, n)最多追加 n 个字符从 src 到 dest。会添加一个空终止符。比 strcat 安全,但要确保 dest 缓冲区大小能容纳原始长度 + n + 1。
strcmp(s1, s2)按字典顺序比较字符串 s1 和 s2。如果相等返回 0,如果 s1<s2 返回 <0,如果 s1>s2 返回 >0。如果两个字符串都正确空终止,通常是安全的。
strncmp(s1, s2, n)最多比较 s1 和 s2 的前 n 个字符。用于比较可能未空终止的缓冲区或前缀的更安全版本。
strchr(s, c)返回字符 c 在字符串 s 中第一次出现位置的指针(pointer);如果未找到,返回 NULL。如果 s 空终止,则是安全的。
strstr(haystack, needle)返回字符串 needle 在字符串 haystack 中第一次出现位置的指针;如果未找到,返回 NULL。如果两个字符串都空终止,则是安全的。

**最佳实践(Best Practice):**尽可能使用包含大小参数的函数(如 strncpy, snprintf, fgets),并始终注意你的缓冲区大小以防止溢出。在使用不安全函数之前手动检查长度,或动态分配(dynamically allocate)足够的内存(memory)。

使用更安全实践的示例:

#include <stdio.h>
#include <string.h>
#include <stddef.h> // For size_t
int main(void) {
char firstName[] = "Ada";
char lastName[] = "Lovelace";
char fullName[50]; // Ensure buffer is large enough
size_t lenFirstName = strlen(firstName);
size_t lenLastName = strlen(lastName);
size_t bufferSize = sizeof(fullName);
// Use snprintf for safe concatenation (recommended)
int written = snprintf(fullName, bufferSize, "%s %s", firstName, lastName);
if (written > 0 && (size_t)written < bufferSize) {
printf("Full Name (snprintf): %s\n", fullName);
printf("Length: %zu\n", strlen(fullName)); // Or use 'written'
} else {
fprintf(stderr, "Error creating full name or buffer too small.\n");
}
// --- Alternative using strncpy/strncat (more manual) ---
char fullNameManual[50];
// Ensure space for first name, space, last name, null terminator
if (lenFirstName + 1 + lenLastName + 1 <= sizeof(fullNameManual)) {
strncpy(fullNameManual, firstName, sizeof(fullNameManual) - 1);
fullNameManual[sizeof(fullNameManual) - 1] = '\0'; // Ensure null termination
strncat(fullNameManual, " ", sizeof(fullNameManual) - strlen(fullNameManual) - 1);
strncat(fullNameManual, lastName, sizeof(fullNameManual) - strlen(fullNameManual) - 1);
printf("Full Name (manual): %s\n", fullNameManual);
printf("Length: %zu\n", strlen(fullNameManual));
} else {
fprintf(stderr, "Manual buffer too small.\n");
}
return 0;
}

输出 (Output):

Full Name (snprintf): Ada Lovelace
Length: 11
Full Name (manual): Ada Lovelace
Length: 11

在 C 语言中,正确处理字符串至关重要,以避免安全漏洞(security vulnerabilities)和程序崩溃(program crashes)。