【问题标题】:Printing number as string in C without stdlibs在没有标准库的情况下在 C 中将数字打印为字符串
【发布时间】:2023-03-21 03:12:01
【问题描述】:

我正在尝试做类似的事情 printf("My age is: %d\n", age); 但我无权访问标准库。相反,我可以访问的打印功能print() 只需要const char*

我不允许使用 sprintfitoa 或任何类似的东西,因为那根本不可用。有什么方法可以打印号码吗?

附:如果您需要更多上下文,https://github.com/SpinalHDL/VexRiscv/issues/215 它是:)

提前致谢

【问题讨论】:

  • 那台机器上有 Unix 操作系统吗?因为我得到的印象是裸机。
  • 这不是一个真正的 unix/linux 问题,更多的是如何编写 itoa
  • 这通常使用 recursion 完成,但没有一个答案这样做。
  • @ThomasDickey 为我的答案添加了递归解决方案。
  • fwiw,当我写this 时,我也做了一个递归函数来转换罗马数字。

标签: c printf


【解决方案1】:

这有点笨拙。您可以从低端开始获取数字,使用 % 10(模数)来隔离个位,使用 /10(整数除法)将数字向右移动。您在循环中执行此操作,直到 int 降至零。如果整个值为零,您必须自己设置 '0' 字符,因为循环不会进行第一次迭代。

您需要为每个数字添加“0”以使其成为 ASCII 数字,并且您需要将连续的数字存储在 char 数组中。

您需要在末尾附加一个 NUL ('\0') 字符作为字符串终止符。

然后你需要反转整个字符串,因为数字以相反的顺序出现。或者,您可以从末尾填充 char 数组,但是您必须将整个字符串(包括 NUL)复制到缓冲区的前面。

如果整数可以为负数,您需要记住这一点,通过从零中减去它使其为正数,并在反转它之前在末尾加上一个“-”。

听起来很多,但是 long2str 函数大约需要 20 行。

编辑:也有递归解决方案。降低所需的深度,并在返回时保存数字,避免逆序问题(包括减号),并创建没有填充的字符串。

//.. Recursive solution.

//.. Base case.

void l2s_r (char **p, long int n)

{
    char d = (n % 10) + '0';
    if (n >= 10) l2s_r (p, n / 10);
    *((*p)++) = d;
}

//.. Wrapper case.

char *l2s (long int n)

{
static char s[24];
char *p = s;

    if (n < 0) { *p++ = '-'; n = 0 - n; }
    l2s_r (& p, n);
    *p = '\0';
    return (s);
}

【讨论】:

  • 我检查了来自 glibc 的 itoa 源:它们从缓冲区末尾的指针开始,然后添加每个数字,将指针向后移动。参见sourceware.org/git/?p=glibc.git;a=blob;f=sysdeps/generic/… 第 84 行的第 80-82 行,指针刚刚返回。剩下的标志处理。 (请注意,链接中的函数更复杂,因为它处理不同的基数 - 十六进制时 - 不同的情况)。
  • @FrédéricLoyer 很好找。这以另一种方式笨拙:它取决于调用者分配足够大小的缓冲区,并传入结束地址和正值或无符号值。它不是用户级函数——第一行是internal。调用者(可能是 printf)需要为每个 arg 值有一个单独的缓冲区,或者将每个值的字符串复制到其他地方。
  • @Paul_Pedant 很容易知道一个数字有多少位。只是几个比较,可以写成2行循环...
  • "但是你必须将整个字符串(包括 NUL)复制到缓冲区的前面。" --> 不是真的。只需print(buffer + offset_to_beginning)
  • “通过从零中减去它来使其为正”有一个角落问题:当x == INT_MIN 等时,0-x 导致 UB。替代方案:“通过从零中减去它来使其为负”。目标是从整数范围的一侧工作。它不一定是积极的一面(尽管这在概念上更容易),消极的一面更大。 IAC,不错的非代码答案。
【解决方案2】:

已经一周了,(不喜欢为深夜硬件作业提供代码),所以一些代码来补充@Paul_Pedant描述性答案。

一种方法,以 2 到 36 为基数,从右到左形成数字字符串。

它通过形成负绝对值来避免0 - INT_MIN 的未定义行为。

它使用div() 而不是/, % 来避免C89 的实现行为。

使用了do 循环,因此print_int(0, base) 打印"0"

#include <assert.h>
#include <limits.h>
#include <stddef.h>
#include <stdlib.h>
extern int print(const char *);

// Enough room for INT_MIN as a binary string.
#define MY_ITOA_BUF_SIZE (sizeof(int)*CHAR_BIT + 2)
#define MY_ITOA_BASE_MIN 2
#define MY_ITOA_BASE_MAX 36

// Negative values result in a string beginning with a '-' for all bases.
// Adjust as desired.
void print_int(int i, int base) {
  char buf[MY_ITOA_BUF_SIZE];
  char *p = &buf[sizeof buf - 1]; // Start from the "right".
  assert(base >= MY_ITOA_BASE_MIN && base <= MY_ITOA_BASE_MAX);

  *p = '\0';
  int value = i < 0 ? i : -i;  // negative absolute value
  do {
    div_t qr = div(value, base);
    *(--p) = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ"[-qr.rem];  // Table look up
    value = qr.quot;
  } while (value);

  if (i < 0) {
    *(--p) = '-';
  }

  print(p);  // Like fputs(p, stdout)
}

【讨论】:

【解决方案3】:

类似的东西

int length(long long int no) 
{
 int count = 0;
 while (no != 0) 
 {
    no = no/10;
        count++;
 }
 return count;
}

void printer(long long int no)
{
   long long int temp = no;
   int i=0;
   char arr[10000];    
   i = length(no)-1;
   //Extract digits and fill `arr` backwards
   while(temp > 0)
   {
        arr[i] = (temp%10 + '0');    
        i--;
        temp = temp/10;
   }
   arr[length(no)] = '\n'; 
  char* p = &arr[0];
  println(p); //specific to: https://github.com/SpinalHDL/VexRiscv/blob/master/src/main/c/murax/hello_world/src/main.c
  //return p;
}

【讨论】:

  • 尝试printer(0)printer(some negative value),不打印任何数字。 char arr[10000]; 过大。缺少回报。
猜你喜欢
  • 1970-01-01
  • 2023-03-18
  • 2020-09-13
  • 1970-01-01
  • 1970-01-01
  • 2012-08-02
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多