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语言入门


相关推荐
charlie1145141916 分钟前
STM32F103C8T6单片机硬核原理篇:讨论GPIO的基本原理篇章1——只讨论我们的GPIO简单输入和输出
c语言·stm32·单片机·嵌入式硬件·gpio·数据手册
矿渣渣8 分钟前
int main(int argc, char **argv)C语言主函数参数解析
c语言·开发语言
阿让啊11 分钟前
bootloader+APP中,有些APP引脚无法正常使用?
c语言·开发语言·stm32·单片机·嵌入式硬件
代码AC不AC42 分钟前
【数据结构】队列
c语言·数据结构·学习·队列·深度讲解
小林熬夜学编程44 分钟前
【高并发内存池】第八弹---脱离new的定长内存池与多线程malloc测试
c语言·开发语言·数据结构·c++·算法·哈希算法
CYRUS_STUDIO2 小时前
Frida Hook Android Native 函数
android·c语言·逆向
W说编程2 小时前
《UNIX网络编程卷1:套接字联网API》第5章 TCP客户服务器程序示例
c语言·网络·网络协议·tcp/ip·unix·tcp
双叶8369 小时前
(C语言)虚数运算(结构体教程)(指针解法)(C语言教程)
c语言·开发语言·数据结构·c++·算法·microsoft
序属秋秋秋12 小时前
算法基础_基础算法【高精度 + 前缀和 + 差分 + 双指针】
c语言·c++·学习·算法
想睡hhh12 小时前
c语言数据结构——八大排序算法实现
c语言·数据结构·排序算法