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


相关推荐
DARLING Zero two♡33 分钟前
关于我、重生到500年前凭借C语言改变世界科技vlog.16——万字详解指针概念及技巧
c语言·开发语言·科技
QAQ小菜鸟1 小时前
一、初识C语言(1)
c语言
何曾参静谧2 小时前
「C/C++」C/C++ 之 变量作用域详解
c语言·开发语言·c++
互联网打工人no12 小时前
每日一题——第一百二十一题
c语言
朱一头zcy3 小时前
C语言复习第9章 字符串/字符/内存函数
c语言
此生只爱蛋3 小时前
【手撕排序2】快速排序
c语言·c++·算法·排序算法
何曾参静谧4 小时前
「C/C++」C/C++ 指针篇 之 指针运算
c语言·开发语言·c++
lulu_gh_yu4 小时前
数据结构之排序补充
c语言·开发语言·数据结构·c++·学习·算法·排序算法
~yY…s<#>6 小时前
【刷题17】最小栈、栈的压入弹出、逆波兰表达式
c语言·数据结构·c++·算法·leetcode
EricWang13588 小时前
[OS] 项目三-2-proc.c: exit(int status)
服务器·c语言·前端