C: 字符串基础
C语言里的字符串就像一条以空字符结尾的项链——每颗珠子是一个字符,最后一颗是透明的终止符\0,告诉别人"项链到此结束"。
1. 字符数组与字符串
C语言没有专门的字符串类型,字符串本质是以\0(空字符)结尾的字符数组。\0的ASCII码为0,它标志着字符串的结束。
char str[6] = {'H', 'e', 'l', 'l', 'o', '\0'};
这个数组有6个元素:5个可见字符加1个\0。没有\0,就只是普通字符数组,不能当作字符串使用。
字符串字面量(双引号)自动在末尾添加\0:
char str[] = "Hello";
数组长度为6(5个字符 + 1个\0),编译器自动计算。也可以指定更大长度,多余位置填\0:
char str[10] = "Hello";
str[5]到str[9]都是\0。
'H'是字符(占1字节),双引号"Hello"是字符串(隐含\0),两者不同。
2. 字符串声明与初始化
(1) 字符数组方式
char s1[] = "abc";
char s2[10] = "abc";
char s3[] = {'a', 'b', 'c', '\0'};
三种方式等价于字符串"abc"。注意s3必须手动加\0。
(2) 字符指针方式
const char *s4 = "abc";
s4指向字符串常量的首字符。字符串常量存储在只读区域,尝试修改会导致未定义行为:
s4[0] = 'A';
这是错误的。而字符数组是可修改的:
s1[0] = 'A';
合法,s1的内容变为"Abc"。
const修饰,编译器会在你尝试修改时给出警告。
(3) 指针与数组的区别
char arr[] = "hello";
const char *ptr = "hello";
arr是数组,在栈上分配内存,内容可修改,sizeof(arr)是6ptr是指针,指向只读区的字符串常量,sizeof(ptr)是指针大小(4或8字节)
3. 字符串输入输出
(1) printf和scanf
用%s格式说明符输入输出字符串:
char name[20];
scanf("%s", name);
printf("你好, %s!\n", name);
scanf读字符串遇到空白符(空格、换行、制表符)就停止,所以无法读入包含空格的整行。
scanf不会检查缓冲区大小,输入超过数组长度会导致越界。可以限制读取长度:
scanf("%19s", name);
最多读19个字符,留1个位置给\0。
(2) puts和gets
puts输出字符串并自动加换行:
char msg[] = "Hello";
puts(msg);
等价于printf("%s\n", msg);。
gets读取一整行(包括空格),直到换行符:
char line[100];
gets(line);
(3) fgets
fgets是gets的安全替代:
char line[100];
fgets(line, sizeof(line), stdin);
参数依次为:存储缓冲区、最大读取字节数(含\0)、输入流。fgets会保留换行符在字符串末尾(如果读到了换行符且还有空间)。
去除末尾换行符的常用写法:
#include <stdio.h>
#include <string.h>
int main(void) {
char line[100];
fgets(line, sizeof(line), stdin);
int len = strlen(line);
if (len > 0 && line[len - 1] == '\n') {
line[len - 1] = '\0';
}
printf("[%s]\n", line);
return 0;
}
▶ 示例
读取用户姓名和一行自我介绍:
#include <stdio.h>
#include <string.h>
void trim_newline(char *s) {
int len = strlen(s);
if (len > 0 && s[len - 1] == '\n') {
s[len - 1] = '\0';
}
}
int main(void) {
char name[30];
char intro[200];
printf("请输入姓名: ");
fgets(name, sizeof(name), stdin);
trim_newline(name);
printf("请输入自我介绍: ");
fgets(intro, sizeof(intro), stdin);
trim_newline(intro);
printf("\n--- 个人信息 ---\n");
printf("姓名: %s\n", name);
printf("介绍: %s\n", intro);
return 0;
}
请输入姓名: 张三
请输入自我介绍: 我是一名C语言初学者
--- 个人信息 ---
姓名: 张三
介绍: 我是一名C语言初学者
4. strlen函数
strlen声明在<string.h>中,返回字符串的长度(不含\0):
#include <string.h>
char s[] = "Hello";
printf("%zu\n", strlen(s));
输出5。注意sizeof(s)是6(含\0),strlen(s)是5(不含\0)。
strlen的工作原理是从首字符开始逐个计数,直到遇到\0:
size_t my_strlen(const char *s) {
size_t len = 0;
while (s[len] != '\0') {
len++;
}
return len;
}
如果字符数组没有\0结尾,strlen会一直读下去,直到偶然遇到0字节,这是很危险的。
5. 字符指针初识
字符指针可以指向字符数组或字符串常量:
char arr[] = "world";
const char *ptr = "world";
ptr = arr;
printf("%s\n", ptr);
指针可以被重新赋值指向不同的字符串,而数组名是常量不可修改:
ptr = "another string";
合法。但:
arr = "another string";
非法,数组名不能赋值。
遍历字符串的指针方式:
const char *p = "Hello";
while (*p != '\0') {
printf("%c", *p);
p++;
}
printf("\n");
*p解引用得到当前字符,p++移动到下一个字符的位置。指针的步长由指向类型决定,char *每步移动1字节。
▶ 示例
用字符指针实现字符串逆序输出:
#include <stdio.h>
#include <string.h>
void print_reverse(const char *s) {
const char *p = s + strlen(s) - 1;
while (p >= s) {
putchar(*p);
p--;
}
putchar('\n');
}
int main(void) {
print_reverse("Hello");
print_reverse("ABCDEF");
return 0;
}
olleH
FEDCBA
先让指针p跳到字符串最后一个字符(\0前面一位),然后从后向前逐个输出。
❓ 常见问题
'a'和"a"有什么区别?'a'是字符常量,占1字节;"a"是字符串常量,占2字节('a'加'\0'),类型是char[]。sizeof和strlen对字符串结果为什么不同?sizeof返回数组占内存总字节数(含\0),strlen返回\0之前的字符个数。fgets读到换行符怎么办?fgets会把换行符存入缓冲区。通常需要手动检查并替换为\0来去除。📖 小节
- C字符串是以
\0结尾的字符数组,\0是字符串操作函数判断结束的依据 - 字符数组可修改内容,字符指针指向常量时不可修改
- 用
fgets代替gets读取字符串,始终指定缓冲区大小 strlen返回有效字符数,sizeof返回数组总大小- 字符指针可以遍历字符串,每步移动一个char的位置
📝 作业
- 编写程序,用
fgets读取一行文本,统计其中字母、数字和其他字符的个数。 - 编写函数
void str_replace_char(char *s, char old, char new_ch),将字符串s中所有old字符替换为new_ch。 - 编写函数用字符指针方式计算字符串长度,不使用
strlen。