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


相关推荐
福尔摩斯张27 分钟前
《C 语言指针从入门到精通:全面笔记 + 实战习题深度解析》(超详细)
linux·运维·服务器·c语言·开发语言·c++·算法
fashion 道格29 分钟前
数据结构实战:深入理解队列的链式结构与实现
c语言·数据结构
铁手飞鹰2 小时前
二叉树(C语言,手撕)
c语言·数据结构·算法·二叉树·深度优先·广度优先
[J] 一坚3 小时前
深入浅出理解冒泡、插入排序和归并、快速排序递归调用过程
c语言·数据结构·算法·排序算法
散峰而望5 小时前
C++数组(一)(算法竞赛)
c语言·开发语言·c++·算法·github
自然常数e5 小时前
深入理解指针(1)
c语言·算法·visual studio
hazy1k7 小时前
ESP32基础-Socket通信 (TCP/UDP)
c语言·单片机·嵌入式硬件·网络协议·tcp/ip·udp·esp32
dvvvvvw7 小时前
x的y次幂的递归函数.c
c语言
Want59510 小时前
C/C++跳动的爱心②
c语言·开发语言·c++
大牙Adela10 小时前
在Mac上通过Multipass虚拟机中的Ubuntu系统使用Graphviz工具
c语言·qt·ubuntu·macos·multipass·graphviz