【底层重构】C语言100篇:从入门到天花板 第14篇
【底层重构】C语言100篇:从入门到天花板 第14篇 字符数组与字符串:定义、输入输出、常用操作
作者:华夏之光永存
专栏定位:从零起步,直击C语言底层本质,覆盖基础到内核级开发,100篇完整体系化教学
前言
大家好,欢迎继续学习CSDN原创专栏《C语言100篇:从入门到天花板》,本篇是第一阶段基础入门的第14篇,承接上一篇二维数组的知识,正式进入字符数组与字符串的核心学习。在前面的内容中,我们掌握了一维、二维数组的通用用法,而字符数组是数组中最特殊、最常用的分支,专门用于处理文本类数据,也是C语言字符串的载体。
C语言没有独立的字符串类型,字符串本质是末尾带结束符’\0’的字符数组,这是贯穿C语言字符串操作的核心本质。本篇将从字符数组与字符串的本源区别、定义与初始化、输入输出全方案、常用字符串操作、底层内存布局、高频错误规避、企业级编码规范全方位精讲,全文超2800字,从基础用法到底层原理层层递进,搭配大量可直接运行的代码案例,帮你彻底厘清字符数组与字符串的关系,掌握文本数据处理的核心技能,为后续文件操作、函数传参、网络编程打下坚实基础。
学习本篇后,你将彻底掌握C语言文本数据处理方法,告别字符串输入输出的常见坑点,能独立完成文本处理、字符串运算等实用程序,真正理解C语言字符串的底层逻辑。
一、字符数组与字符串的本源:本质区别与核心概念
1.1 字符数组的定义
字符数组是元素类型为char的数组,用于存储多个字符,遵循数组的所有通用规则:内存连续、下标从0开始、可定义一维/二维字符数组,字符数组末尾可以没有结束符,仅作为普通字符集合使用。
语法:
// 一维字符数组
char 数组名[长度];
// 二维字符数组(存储多个字符串)
char 数组名[行数][列数];
1.2 字符串的本质
C语言中没有专门的字符串类型,字符串是以’\0’(空字符,ASCII码值为0)作为结束标志的一维字符数组,‘\0’是字符串的核心标识,系统通过它判断字符串是否结束,没有’\0’的字符数组,不能称之为字符串。
核心区别:
- 字符数组:仅存储字符,无固定结束标志,长度为数组定义的大小;
- 字符串:字符数组+末尾’\0’结束符,有效长度为到’\0’之前的字符个数。
举个直观例子:
// 普通字符数组,无'\0',不是字符串
char arr1[5] = {'h','e','l','l','o'};
// 字符串,末尾隐含'\0',是合法字符串
char arr2[6] = {'h','e','l','l','o','\0'};
// 简写形式,编译器自动在末尾添加'\0'
char arr3[] = "hello";
arr1无法用字符串函数处理,arr2和arr3是标准字符串,可直接使用字符串相关操作函数。
1.3 结束符’\0’的核心作用
- 标记字符串结束:printf、strlen等函数,通过检测’\0’判断字符串是否结束,遇到’\0’立即停止操作;
- ASCII码值:‘\0’的ASCII码是0,与字符’0’(ASCII码48)完全不同,不可混淆;
- 占用内存:'\0’占用1个字节的内存空间,定义字符串数组时,需预留结束符的位置。
二、字符数组与字符串的定义及初始化
2.1 一维字符数组的4种初始化方式
方式1:逐个字符赋值(无’\0’,普通字符数组)
逐个列出所有字符,末尾不添加’\0’,仅作为字符集合,不能当作字符串使用。
// 5个字符,无结束符,数组长度必须≥字符个数
char arr[5] = {'h','e','l','l','o'};
方式2:逐个字符赋值(加’\0’,字符串)
手动添加’\0’,成为合法字符串,数组长度必须≥字符个数+1。
// 6个元素,5个有效字符+1个'\0'
char arr[6] = {'h','e','l','l','o','\0'};
方式3:双引号直接赋值(字符串,最常用)
用双引号包裹字符串,编译器自动在末尾添加’\0’,无需手动写结束符,是企业开发首选方式。
// 编译器自动计算长度为6(5个字符+'\0')
char arr[] = "hello";
// 指定长度,长度≥字符个数+1
char arr[10] = "hello";
方式4:部分初始化(未赋值元素自动补’\0’)
// 前5个字符为h e l l o,剩余元素自动补'\0'
char arr[10] = {'h','e','l','l','o'};
2.2 二维字符数组(存储多个字符串)
二维字符数组用于存储多个字符串,每一行对应一个字符串,语法:
// 3个字符串,每个字符串最多存储9个字符(预留'\0')
char str[3][10] = {"apple", "banana", "orange"};
遍历方式:双层for循环,外层控制字符串个数,内层控制单个字符串的字符。
2.3 初始化核心规则
- 双引号初始化字符串,自动加’\0’,数组长度比有效字符多1;
- 手动逐个赋值,必须加’\0’才是合法字符串;
- 数组长度不足时,编译器报错,严禁数组越界;
- 字符数组区分大小写,'A’和’a’是不同字符。
三、字符数组与字符串的输入输出(全方案+避坑)
字符串输入输出是新手最易踩坑的部分,C语言提供多种输入输出函数,需根据场景选择,同时规避缓冲区、空格、越界等问题。
3.1 基础输出函数
1. printf输出(最常用)
- 输出单个字符:
%c,配合下标; - 输出整个字符串:
%s,遇到’\0’停止输出。
<stdio.h>
int main() {
char str[] = "hello world";
// 输出单个字符
printf("单个字符:%c\n", str[0]);
// 输出整个字符串
printf("整个字符串:%s\n", str);
return 0;
}
2. putchar输出单个字符
// 输出单个字符,自动换行
putchar(str[0]);
3. puts输出整个字符串
// 输出字符串,自动在末尾添加换行符\n
puts(str);
特点:用法简单,自带换行,但无法格式化输出,仅能输出字符串。
3.2 常用输入函数(避坑重点)
1. scanf输入(最常用,坑点最多)
- 输入单个字符:
%c; - 输入字符串:
%s,遇到空格、回车、制表符自动停止,且不会读取换行符。
代码示例:
int main() {
char str[20];
printf("请输入字符串:");
// %s输入,不能带空格
scanf("%s", str);
printf("输入结果:%s\n", str);
return 0;
}
致命坑点:
- 无法输入带空格的字符串(如"hello world",仅能接收"hello");
- 输入字符个数超过数组长度,导致数组越界,程序崩溃;
- 输入后缓冲区残留换行符,影响后续输入。
2. getchar输入单个字符
char ch;
// 读取单个字符,包括空格、回车
ch = getchar();
用途:吸收缓冲区残留的换行符,解决scanf输入遗留问题。
3. gets输入(已废弃,严禁使用)
gets可读取带空格的字符串,但不检查数组长度,极易导致缓冲区溢出,C11标准已彻底废弃,企业开发绝对禁止使用。
4. fgets输入(最安全,企业推荐)
fgets是gets的替代函数,指定读取长度,避免越界,支持读取空格、回车,是字符串输入的最优选择。
语法:
fgets(字符数组名, 读取长度, stdin);
// stdin表示标准输入(键盘)
代码示例:
<stdio.h>
int main() {
char str[20];
printf(“请输入带空格的字符串:”);
// 最多读取19个字符,预留1个位置存’\0’
fgets(str, 20, stdin);
printf(“输入结果:%s”, str);
return 0;
}
**优点**:安全无越界,支持空格输入;**注意**:会读取输入的换行符,可手动去除。
### 3.3 输入输出避坑总结
1. 输出字符串用`%s`,必须保证字符串末尾有'\0',否则输出乱码;
2. 输入无空格字符串用`scanf("%s")`,输入带空格字符串用`fgets`;
3. 严禁使用`gets`函数,杜绝缓冲区溢出;
4. `scanf`输入后,用`getchar()`吸收缓冲区换行符;
5. 输入长度必须小于数组长度,预留'\0'位置。
---
## 四、字符串常用操作(手动实现+库函数)
C语言提供了丰富的字符串处理库函数,需包含头文件<string.h>`,同时掌握手动实现逻辑,理解底层原理。
### 4.1 字符串长度计算
#### 库函数:strlen
**作用**:计算字符串有效长度(**不包含'\0'**)。
```<stdio.h<string.h>
int main() {
char str[] = "hello";
// 结果为5,不包含'\0'
int len = strlen(str);
printf("字符串长度:%d\n", len);
// 数组长度:6(包含'\0')
printf("数组长度:%zu\n", sizeof(str));
return 0;
}
核心区别:strlen是有效字符数,sizeof是数组总字节数。
手动实现strlen
int my_strlen(char str[]) {
int len = 0;
// 遍历到'\0'停止
while (str[len] != '\0') {
len++;
}
return len;
}
4.2 字符串拷贝
库函数:strcpy
作用:将一个字符串完整拷贝到另一个字符数组,自动拷贝’\0’。
<stdio.h>
<string.h>
int main() {
char str1[20] = "hello";
char str2[20];
// 将str1拷贝到str2
strcpy(str2, str1);
printf("拷贝结果:%s\n", str2);
return 0;
}
坑点:目标数组长度必须≥源字符串长度+1,否则越界。
4.3 字符串拼接
库函数:strcat
作用:将一个字符串拼接到另一个字符串末尾,自动覆盖目标字符串末尾的’\0’,并添加新的’\0’。
<stdio<string.h>
int main() {
char str1[20] = "hello “;
char str2[] = “world”;
// 拼接后str1为"hello world”
strcat(str1, str2);
printf(“拼接结果:%s\n”, str1);
return 0;
}
### 4.4 字符串比较
#### 库函数:strcmp
**作用**:按ASCII码值逐字符比较两个字符串,区分大小写。
- 返回值=0:两个字符串相等;
- 返回值>0:第一个字符串大于第二个;
- 返回值<0:第一个字符串小于第二个。
```c
<stdio.h>
<string.h>
int main() {
char str1[] = "apple";
char str2[] = "banana";
int res = strcmp(str1, str2);
if (res == 0) {
printf("两个字符串相等\n");
} else if (res > 0) {
printf("str1大于str2\n");
} else {
printf("str1小于str2\n");
}
return 0;
}
4.5 常用字符串操作总结
| 函数 | 头文件 | 作用 | 核心要点 |
|---|---|---|---|
| strlen | string.h | 计算字符串长度 | 不含’\0’,遇’\0’停止 |
| strcpy | string.h | 字符串拷贝 | 目标数组足够大,自动拷贝’\0’ |
| strcat | string.h | 字符串拼接 | 目标数组预留足够空间 |
| strcmp | string.h | 字符串比较 | 按ASCII码,区分大小写 |
五、字符数组与字符串的底层内存布局
5.1 一维字符串内存布局
以char str[] = "hello"为例,内存连续存储,共6个字节:
下标:0 1 2 3 4 5
字符:h e l l o \0
ASCII:104 101 108 108 111 0
特点:
- 内存连续,每个字符占1字节;
- 末尾’\0’是字符串的核心标识;
- 字符串常量存储在常量区,只读不可修改。
5.2 字符数组与字符串指针的区别
// 字符数组,存储在栈区,可修改内容
char arr[] = "hello";
// 字符串指针,指向常量区字符串,不可修改
char *p = "hello";
核心区别:数组内容可修改,指针指向的常量字符串不可修改,否则程序崩溃。
六、高频错误与避坑指南(新手99%必踩)
6.1 字符串末尾缺少’\0’,输出乱码
错误示例:
char arr[5] = {'h','e','l','l','o'};
printf("%s", arr); // 无'\0',输出乱码+垃圾值
避坑:用双引号初始化字符串,或手动添加’\0’。
6.2 混淆strlen与sizeof的结果
错误认知:认为两者结果相同,实际strlen是有效长度,sizeof是数组总长度。
6.3 scanf输入带空格字符串,接收不完整
错误示例:
scanf("%s", str); // 输入"hello world",仅接收"hello"
避坑:用fgets函数输入带空格字符串。
6.4 修改字符串指针指向的常量字符串
错误示例:
char *p = "hello";
p[0] = 'H'; // 崩溃,常量区不可修改
避坑:用字符数组存储可修改的字符串。
6.5 字符串拷贝/拼接时,目标数组长度不足
错误示例:
char str1[5] = "hello";
char str2[3] = "hi";
strcpy(str1, str2); // 越界,程序崩溃
避坑:目标数组长度≥源字符串长度+1。
6.6 使用废弃函数gets
避坑:一律使用fgets替代gets。
七、企业级字符数组与字符串编码规范
- 字符串初始化优先用双引号,编译器自动添加’\0’,杜绝手动遗漏;
- 输入字符串优先用fgets,安全无越界,支持空格输入;
- 定义字符数组时,长度预留充足空间,至少比有效字符多2;
- 处理字符串必须<string.h>`头文件,优先使用标准库函数;
- 区分字符数组与字符串指针,可修改字符串用数组,只读字符串用指针;
- 字符串操作前,先判断是否为NULL,避免空指针;
- 禁止直接修改字符串常量,所有可修改文本用字符数组存储;
- 二维字符数组存储多个字符串时,列数统一,便于维护。
八、高频实战案例
案例1:统计字符串中字母、数字、空格个数
<stdio<string.h>
int main() {
char str[100];
int letter = 0, num = 0, space = 0;
printf(“请输入字符串:”);
fgets(str, 100, stdin);
int len = strlen(str);
for (int i =< len; i++) {
if ((str[i] >= ‘a’ && str[i<= ‘z’) || (str[i] >= ‘A’ && str[i]<= ‘Z’)) {
letter++;
} else if (str[i] >= ‘0’ && str<= ‘9’) {
num++;
} else if (str[i] == ’ ') {
space++;
}
}
printf(“字母:%d个,数字:%d个,空格:%d个\n”, letter, num, space);
return 0;
}
### 案例2:字符串反转(手动实现)
```c
#include<stdio.h>
#include<string.h>
int main() {
char str[] = "hello world";
int len = strlen(str);
char temp;
// 首尾字符交换
for (int i = 0< len / 2; i++) {
temp = str[i];
str[i] = str[len - 1 - i];
str[len - 1 - i] = temp;
}
printf("反转后字符串:%s\n", str);
return 0;
}
案例3:判断字符串是否为回文字符串
int main() {
char str[100];
printf("请输入字符串:");
fgets(str, 100, stdin);
int len = strlen(str);
// 去除fgets读取的换行符
if (str[len - 1] == '\n') str[len - 1] = '\0';
len = strlen(str);
int flag = 1;
for (int i = 0;< len / 2; i++) {
if (str[i] != str[len - 1 - i]) {
flag = 0;
break;
}
}
if (flag) {
printf("是回文字符串\n");
} else {
printf("不是回文字符串\n");
}
return 0;
}
九、本篇核心知识点总结
- C语言无独立字符串类型,字符串是末尾带’\0’的字符数组;
- '\0’是字符串结束符,ASCII码为0,占用1字节内存,是字符串的核心标识;
- 字符数组初始化:双引号最常用,自动添加’\0’,手动赋值需添加’\0’;
- 输入输出:
printf/puts输出,fgets是最安全的输入函数,严禁用gets; - 常用库函数:strlen(长度)、strcpy(拷贝)、strcat(拼接)、strcmp(比较);
- 底层内存:字符串内存连续,常量区字符串不可修改,栈区字符数组可修改;
- 核心坑点:缺少’\0’、数组越界、混淆strlen与sizeof、修改字符串常量;
- 企业规范:优先用双引号初始化、fgets输入、标准库函数处理字符串。
字符数组与字符串是C语言处理文本数据的核心,也是后续文件操作、网络编程、函数传参的基础,彻底掌握其本质与操作规范,才能真正玩转C语言文本处理。
课后实战作业
- 定义一个字符数组,存储字符串"I love C language",计算其长度并输出;
- 用fgets输入一个带空格的字符串,去除其中的空格后输出;
- 手动实现字符串拷贝函数,不使用strcpy;
- 输入两个字符串,比较是否相等,输出比较结果;
- 输入一个字符串,统计其中小写字母的个数,并将其转为大写字母输出。
下期预告
第15篇《函数基础:函数定义、声明、调用、返回值》,将进入C语言模块化编程的核心章节,详解函数的完整用法、参数传递、返回值设计,覆盖函数封装、代码复用的核心技巧,延续高质量干货标准,敬请期待!
专栏关注钩子
本专栏为**《C语言100篇:从入门到天花板》**,100篇完整体系,覆盖基础入门、进阶核心、底层原理、天花板深度四大阶段,从零基础直达嵌入式、内核、高性能开发领域,每篇超2000字纯干货,兼顾底层原理与企业实战。关注作者华夏之光永存,第一时间获取更新,配套源码、面试题库、实战项目持续分享,带你系统性吃透C语言,从小白进阶到行业天花板!
更多推荐
所有评论(0)