2023-10-16 itoa函数的局限以及实现


点击 <C 语言编程核心突破> 快速C语言入门


itoa函数的局限以及实现


前言

把一个数用某种进制打印, 是一个很有用的功能,

值得庆幸的是, C语言有这么一个函数itoa(), 它可以把一个数转换为2至36进制的字符串,

但不那么幸运的是, 这个函数只是存在于某些编译器中, 比如Windows下的clang, 但Linux下, 则没有这个函数.

不过这也不是什么问题, 既然选择用C语言, 造轮子就是程序员的命运, 做个函数实现, 并不困难.


一、功能描述

itoa()函数需要传入三个参数,

一个无符号整数num, 基于num进行进制转换,

一个字符指针string, 为了安全, 至少需要33字节, 因为我们要实现一个转换32位的函数, 如果是2进制, 最大需要32位, 外加一个末尾0,

一个进制限制radix, 从2到36, 再多也可以, 但是意义不大.

函数返回赋值后的string字符指针, 因此函数原先应该是:

c 复制代码
char *itoa(uint32_t num, char *string, uint32_t radix);

二、具体实现

对于第一版, 实现如下:

c 复制代码
#include <stdint.h>
#include <string.h>

char *itoa(uint32_t num, char *string, uint32_t radix)
{
    static char arr[40];
    arr[0] = '0';

    static int index;
    index = 31;

    while (num)
    {
        static uint32_t numA;
        numA = num % radix;
        arr[index--] = (char)(numA > 9 ? numA - 10 + 'a' : numA + '0');
        num /= radix;
    }

    static char *rest;
    rest = arr + (index == 31 ? 0 : index + 1);

    strncpy(string, rest, 32 - index);

    return string;
}

但有两个问题可以进行改进,

其一, 进制限制没有判断, 如果是超过2至36的其它数则会出现比较奇怪的输出, 需要打个补丁.

其二, 基于效率, 字符转换用了三目运算, 这个可能比较吃效率, 需要优化.

第二版实现:

c 复制代码
#include <stdint.h>
#include <string.h>

char *itoa(uint32_t num, char *string, uint32_t radix)
{
    // 如果进制限制超出范围, 则返回空字符串
    if (radix < 2 || radix > 36)
    {
        string[0] = '\0';
        return string;
    }

    // 如果num等于0, 则直接赋值为0, 返回
    if (num == 0)
    {
        string[0] = '0';
        string[1] = '\0';
        return string;
    }

    // 建立一个buffer, 40的空间足够容纳所有转换字符
    static char arr[40];

    // 建立一个索引, 用于从后向前填充字符
    static int index;
    index = 31;

    while (num)
    {
        // 直接通过数组引用赋值, 免去分支以及计算
        arr[index--] = "0123456789abcdefghijklmnopqrstuvwxyz"[num % radix];
        num /= radix;
    }

    // 这个函数的参数比较值得关注
    // arr + index + 1 是指向最后被赋值的字符位置的指针
    // 32 - index 是赋值字符数量 + 1 多一个字符位是给末尾0的
    strncpy(string, (arr + index + 1), (32 - index));

    return string;
}

实现中没有考虑到有符号的问题, 因为C语言中, 本身十六进制或二进制就没有符号,

当然, 如果有必要设计符号的转换, 也不难, 但函数的形参类型需要转换, 大家可以自己研究.

测试用例:

c 复制代码
#include <stdint.h>
#include <stdio.h>
#include <string.h>

char *itoa(uint32_t num, char *string, uint32_t radix);

int main()
{
    char str[40] = "abcdabcdabcdabcdabcdabcdabcdabcdabcd";
    itoa(255, str, 16);
    itoa(8, str, 16);
    itoa(-1, str, 2);
    itoa(0, str, 2);
    itoa(0, str, 1);

    return 0;
}

char *itoa(uint32_t num, char *string, uint32_t radix)
{
    // 如果进制限制超出范围, 则返回空字符串
    if (radix < 2 || radix > 36)
    {
        string[0] = '\0';
        return string;
    }

    // 如果num等于0, 则直接赋值为0, 返回
    if (num == 0)
    {
        string[0] = '0';
        string[1] = '\0';
        return string;
    }

    // 建立一个buffer, 40的空间足够容纳所有转换字符
    static char arr[40];

    // 建立一个索引, 用于从后向前填充字符
    static int index;
    index = 31;

    while (num)
    {
        // 直接通过数组引用赋值, 免去分支以及计算
        arr[index--] = "0123456789abcdefghijklmnopqrstuvwxyz"[num % radix];
        num /= radix;
    }

    // 这个函数的参数比较值得关注
    // arr + index + 1 是指向最后被赋值的字符位置的指针
    // 32 - index 是赋值字符数量 + 1 多一个字符位是给末尾0的
    strncpy(string, (arr + index + 1), (32 - index));

    return string;
}

总结

设计一个可用的库函数比想象的还是要难一些, 需要考虑到很多问题, 兼容性, 可移植性, 效率, 以及使用者的理解, 从这一个小小的函数实现, 就可见难度之大, 所以说, 有时候认为自己设计的东西随便就能碾压库函数, 那可能是梦的深沉, 没有睡醒.


点击 <C 语言编程核心突破> 快速C语言入门


相关推荐
kingmax542120088 小时前
《数据结构C语言:单向链表-链表基本操作(尾插法建表、插入)》15分钟试讲教案【模版】
c语言·数据结构·链表
mit6.8249 小时前
[box64] 解决ARM64运行x86_64跨平台兼容性 | 机器架构配置
c语言
喵了meme10 小时前
C语言实战6
c语言·开发语言
Logic10110 小时前
C程序设计(第五版)谭浩强 第七章课后习题优化算法与核心步骤解析
c语言·visualstudio·程序员·学习笔记·软件开发·编程基础·c语言入门
喵了meme10 小时前
Linux学习日记21:读写锁
linux·c语言·学习
风中月隐11 小时前
C语言中以坐标的方式图解“字母金字塔”的绘制
c语言·开发语言·算法·字母金子塔·坐标图解法
NeDon12 小时前
[OJ]数据结构:移除链表元素
c语言·数据结构·算法·链表
刃神太酷啦12 小时前
C++ list 容器全解析:从构造到模拟实现的深度探索----《Hello C++ Wrold!》(16)--(C/C++)
java·c语言·c++·qt·算法·leetcode·list
Herbert_hwt12 小时前
C语言结构体详解:从定义到实战应用
c语言
承渊政道12 小时前
一文彻底搞清楚链表算法实战大揭秘和双向链表实现
c语言·数据结构·算法·leetcode·链表·visual studio