C 语言实战:堆内存存储字符串 + 多种递归方案计算字符串长度
本文承接给定代码的核心需求(堆内存申请 + 递归求字符串长度),先指出原代码的核心问题,再提供3 种不同思路的递归实现方案,同时完善程序的健壮性,帮你吃透递归的核心逻辑与字符串操作的细节。
一、原代码核心问题解析
先明确原递归函数fnc的关键错误,避免踩坑:
- 递归终止条件返回值错误 :当
*a == '\0'(字符串结束符)时,说明已经遍历到字符串末尾,该位置不计入有效长度,应返回0而非1,原代码会导致计算结果比实际长度多 1; - 递归逻辑的小瑕疵 :
fnc(a+1)+1的逻辑本身可行(后续遍历结果 + 当前字符),但因终止条件错误,最终结果失真; - 额外优化点 :
scanf("%s", a)无法读取含空格的字符串,可保留该写法(符合题目要求),同时补充说明替代方案。
二、完整程序框架(堆内存申请 + 释放)
先搭建通用的程序框架,堆内存的申请、判断、释放是固定规范,所有递归方案都基于此框架运行:
c
运行
c
/******************************
*文件名称:2.String_length.c
*作者:czy
*邮箱:caozhiyang_0613@163.com
*创建日期:2026/1/4
*修改日期:2026/01/11
*文件功能:堆内存存储字符串,多种递归方案计算字符串实际长度
*****************************/
#include<stdio.h>
#include<stdlib.h>
#include<string.h>
// 后续递归函数声明(根据不同方案替换)
int str_len_recur1(char *a);
int str_len_recur2(char *a);
int str_len_recur3(char *a, int count);
int main()
{
char *a = NULL;
int len = 0;
// 1. 申请堆内存(大小100字节,足够存储常规输入字符串)
a = (char *)malloc(100 * sizeof(char));
if(a == NULL) // 健壮性判断:内存分配失败直接退出
{
printf("内存分配失败!\n");
return 1;
}
// 2. 键盘输入字符串(scanf("%s"):不支持空格,符合题目要求)
printf("请输入一串字符(不含空格):\n");
scanf("%s", a);
// 3. 调用递归函数计算长度(可切换不同递归方案)
len = str_len_recur1(a); // 方案1
// len = str_len_recur2(a); // 方案2
// len = str_len_recur3(a, 0); // 方案3
// 4. 输出结果
printf("字符串的实际长度为:%d\n", len);
// 5. 释放堆内存,避免内存泄漏
free(a);
a = NULL; // 置空避免野指针
return 0;
}
三、3 种递归方案实现(求字符串长度)
方案 1:基础递归(指针偏移 + 终止条件返回 0)
这是最简洁、最常用的递归方案,修正原代码的错误,核心是 "指针后移遍历,遇到结束符返回 0,否则后续结果 + 1"。
c
运行
arduino
// 方案1:基础递归(无额外参数,指针偏移遍历)
int str_len_recur1(char *a)
{
// 终止条件:遇到字符串结束符'\0',返回0(不计入长度)
if(*a == '\0')
{
return 0;
}
// 递归逻辑:当前字符有效(+1),继续遍历下一个字符(a+1)
else
{
return str_len_recur1(a + 1) + 1;
}
}
工作原理(以字符串 "abc" 为例)
- 调用
str_len_recur1("abc"):*a='a'≠'\0',返回str_len_recur1("bc")+1; - 调用
str_len_recur1("bc"):*a='b'≠'\0',返回str_len_recur1("c")+1; - 调用
str_len_recur1("c"):*a='c'≠'\0',返回str_len_recur1("")+1; - 调用
str_len_recur1(""):*a='\0',返回0; - 回溯计算:
0+1+1+1=3,最终结果为 3(正确)。
方案 2:递归 + 下标索引(模拟数组遍历)
该方案引入下标变量i,通过 "指针 + 下标" 的方式遍历字符串,递归的核心是 "下标递增,直到对应字符为 '\0'",适合习惯数组下标操作的开发者。
c
运行
arduino
// 方案2:递归+下标索引(封装辅助函数,对外接口简洁)
// 对外接口:仅需传入字符串指针
int str_len_recur2(char *a)
{
// 辅助递归函数:传入字符串和当前下标
int helper(char *a, int i);
return helper(a, 0); // 下标从0开始遍历
}
// 辅助递归函数(内部使用,处理下标递增)
int helper(char *a, int i)
{
// 终止条件:下标i对应的字符是'\0',返回0
if(a[i] == '\0')
{
return 0;
}
// 递归逻辑:当前下标字符有效(+1),下标i+1继续遍历
return helper(a, i + 1) + 1;
}
特点
- 对外接口简洁(与方案 1 一致,仅需传入字符串指针),内部通过辅助函数处理下标;
- 核心逻辑与方案 1 一致,只是遍历方式从 "指针偏移" 改为 "下标递增",本质都是遍历字符串的每个有效字符;
- 同样以 "abc" 为例,最终回溯结果为
0+1+1+1=3,计算准确。
方案 3:递归 + 累加器(尾递归优化思路)
该方案引入累加器count,用于记录当前已遍历的有效字符数,递归的核心是 "遍历到下一个字符,累加器 + 1,直到遇到 '\0' 返回累加器",属于尾递归(递归调用在函数最后一步,无后续计算),部分编译器可对其进行优化,避免栈溢出。
c
运行
arduino
// 方案3:递归+累加器(尾递归,带计数参数)
int str_len_recur3(char *a, int count)
{
// 终止条件:遇到'\0',返回累加器count(已记录的有效字符数)
if(*a == '\0')
{
return count;
}
// 尾递归逻辑:指针后移(a+1),累加器+1,无后续回溯计算
return str_len_recur3(a + 1, count + 1);
}
使用说明
- 调用时需传入累加器初始值
0:len = str_len_recur3(a, 0); - 工作原理(以 "abc" 为例):
str_len_recur3("abc", 0):*a='a'≠'\0',调用str_len_recur3("bc", 1);str_len_recur3("bc", 1):*a='b'≠'\0',调用str_len_recur3("c", 2);str_len_recur3("c", 2):*a='c'≠'\0',调用str_len_recur3("", 3);str_len_recur3("", 3):*a='\0',返回3(直接返回累加器,无需回溯);
- 特点:尾递归无回溯计算,效率更高,适合处理长字符串(减少栈帧占用)。
四、运行结果示例(3 种方案通用)
输入输出示例
plaintext
请输入一串字符(不含空格):
hello
字符串的实际长度为:5
plaintext
请输入一串字符(不含空格):
Clanguage
字符串的实际长度为:9
plaintext
请输入一串字符(不含空格):
1234567890
字符串的实际长度为:10
五、核心知识点总结
-
堆内存操作规范 :
malloc申请→NULL判断→使用→free释放→NULL置空,五步缺一不可,避免内存泄漏和野指针; -
递归求字符串长度的核心:
- 终止条件:必须是
*a == '\0'(或a[i] == '\0'),返回值根据方案调整(0 或累加器); - 递归推进:要么指针偏移(
a+1),要么下标递增(i+1),确保遍历所有有效字符;
- 终止条件:必须是
-
3 种递归方案的对比:
递归方案 优点 缺点 方案 1(基础指针偏移) 简洁高效,无额外参数,易理解 非尾递归,长字符串可能栈溢出 方案 2(下标索引) 贴近数组操作习惯,可读性强 需封装辅助函数,略繁琐 方案 3(尾递归 + 累加器) 无回溯计算,效率高,栈帧占用少 调用时需传入初始累加器,接口不够简洁 -
字符串操作细节 :
scanf("%s", a)遇空格终止,若需读取含空格的字符串,可替换为fgets(a, 100, stdin),并配合换行符处理函数。
六、扩展与优化建议
- 支持含空格字符串 :将
scanf替换为fgets,并添加换行符处理函数,避免换行符被计入长度; - 动态调整堆内存大小 :先读取字符串长度,再通过
realloc调整堆内存大小,避免内存浪费; - 递归深度限制:添加递归深度计数器,避免超长字符串导致栈溢出(或直接使用尾递归方案);
- 非递归对比:实现一个迭代版的字符串长度计算函数,与递归方案对比效率,理解递归与迭代的优劣。
掌握这 3 种递归方案,不仅能解决字符串长度计算问题,还能举一反三,将递归思想应用到链表遍历、树的遍历等更多场景中。递归的核心是 "分解问题 + 终止条件",只要抓住这两点,就能写出高效、正确的递归代码。