这是一个非常核心且重要的概念,因为 C 语言本身并没有一个叫做 string 的原生类型,我们通常所说的 "字符串" 在 C 语言中是通过 字符数组 来实现的。

(图片来源网络,侵删)
char 类型:单个字符
char 是 C 语言的一个基本数据类型,用来存储一个单一的字符。
特点:
- 大小:通常是 1 字节(8 位)。
- 存储:它存储的是字符的 ASCII 码值(一个整数),而不是字符本身,字符
'A'在内存中存储的是整数65。 - 声明和初始化:
char c1 = 'A'; // 使用单引号,表示一个字符 char c2 = 65; // 直接使用 ASCII 码值,效果等同于 'A' char c3 = '\n'; // 特殊字符,如换行符
错误示范:
char c4 = "A"; // 错误!双引号 "..." 表示字符串,会包含一个 '\0' 结尾符,类型不匹配
C 语言中的 "字符串" (string)
如前所述,C 语言没有 string 类型,字符串被实现为以 空字符 ('\0') 结尾的字符数组。
核心概念:字符串的结尾
C 语言用 空字符 ('\0') 来标记一个字符串的结束,这个字符的 ASCII 码值是 0,任何以 '\0' 结尾的字符序列,C 语言都将其视为一个字符串。
如何表示和创建字符串
主要有三种方式:

(图片来源网络,侵删)
字符数组
这是最基本的方式,你需要手动确保数组末尾有一个 '\0'。
// 方式 1a:定义时初始化,编译器会自动添加 '\0'
char str1[] = "Hello";
// 内存布局: ['H', 'e', 'l', 'l', 'o', '\0']
// sizeof(str1) 是 6,因为包含了 '\0'
// 方式 1b:先定义,再逐个赋值
char str2[6]; // 必须预留一个位置给 '\0'
str2[0] = 'H';
str2[1] = 'e';
str2[2] = 'l';
str2[3] = 'l';
str2[4] = 'o';
str2[5] = '\0'; // 必须手动添加结尾符!
// 方式 1c:使用循环赋值
char str3[10];
for (int i = 0; i < 5; i++) {
str3[i] = "Hello"[i]; // "Hello" 是一个常量字符串
}
str3[5] = '\0'; // 仍然需要手动添加
字符指针
字符指针指向字符串的第一个字符(通常是字符串字面量的首地址)。
// 方式 2a:指向一个字符串字面量
// "Hello" 存储在程序的只读数据区,str4 指向它的第一个字符 'H'
char *str4 = "Hello";
// 内存布局: str4 -> ['H', 'e', 'l', 'l', 'o', '\0'] (在只读区)
// 重要区别:
// char str5[] = "Hello"; // str5 是一个可修改的数组
// char *str6 = "Hello"; // str6 是一个指向只读内存的指针,不建议修改其指向的内容
// str6[0] = 'h'; // 这可能导致未定义行为(程序崩溃),因为它试图修改只读内存
// 方式 2b:动态分配内存
char *str7 = (char *)malloc(6 * sizeof(char));
if (str7 != NULL) {
strcpy(str7, "Hello"); // 使用 strcpy 函数来安全地复制字符串
// str7 现在指向一块堆内存,内容是 "Hello"
}
// ... 使用 str7 ...
free(str7); // 记得释放内存
使用 string.h 中的函数处理
<string.h> 头文件提供了大量专门用于处理 C 风格字符串的函数,如 strlen, strcpy, strcat, strcmp 等。
strlen(str):返回字符串的长度(不包括结尾的'\0')。strcpy(dest, src):将src字符串复制到dest中。dest必须有足够的空间。strcat(dest, src):将src字符串连接到dest的末尾。dest必须有足够的空间。strcmp(s1, s2):比较两个字符串,如果相等,返回0;s1小于s2,返回负数;否则返回正数。
示例:

(图片来源网络,侵删)
#include <stdio.h>
#include <string.h> // 必须包含此头文件
int main() {
char greeting[50] = "Hello, "; // 预留足够空间用于连接
char name[] = "World";
// 获取长度
printf("Length of 'name': %zu\n", strlen(name)); // 使用 %zu 打印 size_t 类型
// 连接字符串
strcat(greeting, name);
printf("After strcat: %s\n", greeting); // 输出: Hello, World
// 复制字符串
char copy_str[20];
strcpy(copy_str, greeting);
printf("Copied string: %s\n", copy_str); // 输出: Hello, World
// 比较字符串
if (strcmp(greeting, copy_str) == 0) {
printf("The strings are equal.\n");
}
return 0;
}
char 数组 vs. char * 指针(关键区别)
这是 C 语言初学者最容易混淆的地方,虽然它们在很多情况下可以互换使用,但本质和用法有重要区别。
| 特性 | char name[] (字符数组) |
char *name (字符指针) |
|---|---|---|
| 本质 | 一块连续的、固定大小的内存空间,用于存放字符。 | 一个变量,它存储的是字符串的首地址。 |
| 内存位置 | 通常在栈上分配。 | 可以指向栈、只读数据区或堆上的内存。 |
| 大小 | sizeof(name) 返回数组总大小(包括 '\0')。 |
sizeof(name) 返回指针变量本身的大小(在 64 位系统上是 8 字节)。 |
| 可修改性 | 是可修改的(只要不越界)。 | 如果指向字符串字面量,其内容通常不可修改(只读区),如果指向堆内存,则可修改。 |
| 可变性 | 不可改变指向的地址。name 是一个常量指针。 |
可以改变指向的地址。name 是一个变量指针。 |
示例代码对比:
#include <stdio.h>
#include <string.h>
int main() {
// 1. 字符数组
char arr[] = "Hello";
printf("Array: sizeof(arr) = %zu\n", sizeof(arr)); // 输出 6
// arr = "World"; // 错误!不能给数组重新赋值
strcpy(arr, "World"); // 正确!可以修改数组内容
printf("Array after strcpy: %s\n", arr);
// 2. 字符指针
char *ptr = "Hello";
printf("Pointer: sizeof(ptr) = %zu\n", sizeof(ptr)); // 输出 8 (64位系统)
// ptr[0] = 'h'; // 危险!可能导致段错误,因为 "Hello" 在只读区
ptr = "World"; // 正确!可以改变指针指向的地址
printf("Pointer after re-assignment: %s\n", ptr);
return 0;
}
安全性:缓冲区溢出
由于 C 语言不进行边界检查,直接使用 strcpy, gets, scanf 等函数处理字符串非常危险,容易导致缓冲区溢出,从而引发程序崩溃或安全漏洞。
不安全的例子:
#include <stdio.h>
#include <string.h>
int main() {
char buffer[10];
printf("Enter a long string (e.g., > 10 chars): ");
// gets(buffer); // gets 函数极度危险,已被废弃,因为它不检查缓冲区大小
// scanf("%s", buffer); // scanf 也同样危险,遇到空格就停止,但不会检查长度
// 一个更安全的替代方案
fgets(buffer, sizeof(buffer), stdin);
// fgets 会读取最多 sizeof(buffer)-1 个字符,并自动添加 '\0'
// 但要注意,fgets 会保留换行符 '\n'
printf("You entered: %s\n", buffer);
return 0;
}
现代 C 编程建议:
- 优先使用
fgets替代gets和scanf来读取输入。 - 使用
strncpy替代strcpy,可以指定最大复制长度。 - 使用
snprintf替代sprintf,可以指定最大写入长度。 - 考虑使用更安全的 C 库,如 Microsoft's Safe C Runtime Library 或第三方库。
char:用于存储单个字符,本质是 1 字节的整数。- C 字符串:不是一个独立类型,而是以
'\0'结尾的char数组。 - 两种主要形式:
- 字符数组 (
char str[]):固定大小,内容可修改,地址不可改,在栈上。 - 字符指针 (
char *str):可变地址,指向字符串(可能在只读区或堆上)。
- 字符数组 (
<string.h>:是处理 C 字符串的标准库,提供了丰富的函数。- 安全性:C 字符串操作是不安全的,极易发生缓冲区溢出,必须使用有边界检查的函数(如
fgets,strncpy)并时刻注意数组的大小。
