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


相关推荐
Michael_Good19 分钟前
【计算机网络】C/C++实现解析Wireshark离线数据包,附源码
c语言·c++·计算机网络
冉佳驹1 小时前
数据结构 ——— 快速排序的时间复杂度以及规避最坏情况的方法
c语言·数据结构·算法·排序算法·快速排序算法·三数取中
徐浪老师2 小时前
C语言实现冒泡排序:从基础到优化全解析
c语言·算法·排序算法
是糖不是唐2 小时前
代码随想录算法训练营第五十八天|Day58 图论
c语言·算法·图论
Peter_chq5 小时前
【计算机网络】多路转接之select
linux·c语言·开发语言·网络·c++·后端·select
格雷亚赛克斯8 小时前
黑马——c语言零基础p139-p145
c语言·数据结构·算法
财富探秘者9 小时前
贵州茅台[600519]行情数据接口
大数据·c语言·python·算法·金融·restful
HABuo10 小时前
【数据结构与算法】合并链表、链表分割、链表回文结构
c语言·开发语言·数据结构·c++·学习·算法·链表
带多刺的玫瑰10 小时前
Leecode刷题C语言之网络延迟时间
c语言·开发语言·算法
奇妙之二进制11 小时前
2025年春招修订版《C/C++笔面试系列》(1) C语言经典笔面试题(上)
c语言·c++·面试