C 语言实战:堆内存存储字符串 + 多种递归方案计算字符串长度

C 语言实战:堆内存存储字符串 + 多种递归方案计算字符串长度

本文承接给定代码的核心需求(堆内存申请 + 递归求字符串长度),先指出原代码的核心问题,再提供3 种不同思路的递归实现方案,同时完善程序的健壮性,帮你吃透递归的核心逻辑与字符串操作的细节。

一、原代码核心问题解析

先明确原递归函数fnc的关键错误,避免踩坑:

  1. 递归终止条件返回值错误 :当*a == '\0'(字符串结束符)时,说明已经遍历到字符串末尾,该位置不计入有效长度,应返回0而非1,原代码会导致计算结果比实际长度多 1;
  2. 递归逻辑的小瑕疵fnc(a+1)+1的逻辑本身可行(后续遍历结果 + 当前字符),但因终止条件错误,最终结果失真;
  3. 额外优化点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" 为例)
  1. 调用str_len_recur1("abc")*a='a'≠'\0',返回str_len_recur1("bc")+1
  2. 调用str_len_recur1("bc")*a='b'≠'\0',返回str_len_recur1("c")+1
  3. 调用str_len_recur1("c")*a='c'≠'\0',返回str_len_recur1("")+1
  4. 调用str_len_recur1("")*a='\0',返回0
  5. 回溯计算: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);
}
使用说明
  • 调用时需传入累加器初始值0len = str_len_recur3(a, 0)
  • 工作原理(以 "abc" 为例):
  1. str_len_recur3("abc", 0)*a='a'≠'\0',调用str_len_recur3("bc", 1)
  2. str_len_recur3("bc", 1)*a='b'≠'\0',调用str_len_recur3("c", 2)
  3. str_len_recur3("c", 2)*a='c'≠'\0',调用str_len_recur3("", 3)
  4. str_len_recur3("", 3)*a='\0',返回3(直接返回累加器,无需回溯);
  • 特点:尾递归无回溯计算,效率更高,适合处理长字符串(减少栈帧占用)。

四、运行结果示例(3 种方案通用)

输入输出示例

plaintext

复制代码
请输入一串字符(不含空格):
hello
字符串的实际长度为:5

plaintext

复制代码
请输入一串字符(不含空格):
Clanguage
字符串的实际长度为:9

plaintext

复制代码
请输入一串字符(不含空格):
1234567890
字符串的实际长度为:10

五、核心知识点总结

  1. 堆内存操作规范malloc申请→NULL判断→使用→free释放→NULL置空,五步缺一不可,避免内存泄漏和野指针;

  2. 递归求字符串长度的核心

    • 终止条件:必须是*a == '\0'(或a[i] == '\0'),返回值根据方案调整(0 或累加器);
    • 递归推进:要么指针偏移(a+1),要么下标递增(i+1),确保遍历所有有效字符;
  3. 3 种递归方案的对比

    递归方案 优点 缺点
    方案 1(基础指针偏移) 简洁高效,无额外参数,易理解 非尾递归,长字符串可能栈溢出
    方案 2(下标索引) 贴近数组操作习惯,可读性强 需封装辅助函数,略繁琐
    方案 3(尾递归 + 累加器) 无回溯计算,效率高,栈帧占用少 调用时需传入初始累加器,接口不够简洁
  4. 字符串操作细节scanf("%s", a)遇空格终止,若需读取含空格的字符串,可替换为fgets(a, 100, stdin),并配合换行符处理函数。

六、扩展与优化建议

  1. 支持含空格字符串 :将scanf替换为fgets,并添加换行符处理函数,避免换行符被计入长度;
  2. 动态调整堆内存大小 :先读取字符串长度,再通过realloc调整堆内存大小,避免内存浪费;
  3. 递归深度限制:添加递归深度计数器,避免超长字符串导致栈溢出(或直接使用尾递归方案);
  4. 非递归对比:实现一个迭代版的字符串长度计算函数,与递归方案对比效率,理解递归与迭代的优劣。

掌握这 3 种递归方案,不仅能解决字符串长度计算问题,还能举一反三,将递归思想应用到链表遍历、树的遍历等更多场景中。递归的核心是 "分解问题 + 终止条件",只要抓住这两点,就能写出高效、正确的递归代码。

相关推荐
golang学习记2 小时前
Go 中防止敏感数据意外泄露的几种姿势
后端
君义_noip2 小时前
【模板:字符串哈希】信息学奥赛一本通 1455:【例题1】Oulipo
算法·哈希算法·信息学奥赛·csp-s
czlczl200209252 小时前
Spring Boot 构建 SaaS 多租户架构
spring boot·后端·架构
小码编匠2 小时前
完美替代 Navicat,一款开源免费、集成了 AIGC 能力的多数据库客户端工具!
数据库·后端·aigc
linuxxx1102 小时前
正则匹配应用小案例
数据库·正则表达式
顺流2 小时前
从零实现一个数据结构可视化调试器(一)
后端
掘金者阿豪2 小时前
Redis键值对批量删除全攻略:安全高效删除包含特定模式的键
后端
星浩AI2 小时前
深入理解 LlamaIndex:RAG 框架核心概念与实践
人工智能·后端·python
用户2190326527352 小时前
配置中心 - 不用改代码就能改配置
后端·spring cloud·微服务