使用C,将动态分配的int数组尽可能干净地转换为逗号分隔的字符串

我在C语言方面的经验比在高级语言方面要差得多。 在Cisco,我们使用C,有时我会遇到一些在Java或Python中很容易做到的事情,但在C中很难做到。现在就是其中之一。

我有一个动态分配的无符号整数数组,我需要转换为逗号分隔的字符串进行日志记录。 虽然整数不太可能非常大,但它们在概念上可以是0到4,294,967,295。在Python中,这是一条短线。

my_str = ','.join(my_list) 

人们在C中做到这一点有多优雅? 我提出了一个方法,但这很糟糕。 如果有人知道这样做的好方法,请赐教。

代码现在在gcc下测试和构建。

与其他答案相反,不强制要求C99。

这里真正的问题是不知道你需要的字符串的长度。 获取数字就像sprintf("%u", *num)一样简单sprintf("%u", *num)使用num来遍历你的int数组,但是你需要多少空间? 为避免超出缓冲区,您必须跟踪大量整数。

 size_t join_integers(const unsigned int *num, size_t num_len, char *buf, size_t buf_len) { size_t i; unsigned int written = 0; for(i = 0; i < num_len; i++) { written += snprintf(buf + written, buf_len - written, (i != 0 ? ", %u" : "%u"), *(num + i)); if(written == buf_len) break; } return written; } 

请注意,我记录了我使用了多少缓冲区并使用了snprintf因此我没有超出结尾。 snprintf会在\0 ,但由于我使用的是buf + written我将从前一个snprintf\0开始。

正在使用:

 int main() { size_t foo; char buf[512]; unsigned int numbers[] = { 10, 20, 30, 40, 1024 }; foo = join_integers(numbers, 5, buf, 512); printf("returned %u\n", foo); printf("numbers: %s\n", buf); } 

输出:

 returned 20 numbers: 10, 20, 30, 40, 1024 

强制限制进入,而不是超越:

 char buf[15]; foo = join_integers(numbers, 5, buf, 14); buf[14] = '\0'; 

预计产出:

 returned 14 numbers: 10, 20, 30, 4 

你实际上可以使用像Glib这样的库,它包含像

gchar * g_strjoin(const gchar * separator,…);

将多个字符串连接在一起形成一个长字符串,并在每个字符串之间插入可选的分隔符。 应使用g_free()释放返回的字符串。

(你仍然需要使用g_snprintf() ,可能使用g_printf_string_upper_bound()来确保空间)

那这个呢?

 char *join_int_list(const unsigned int *list, size_t n_items) { enum { SIZEOF_INT_AS_STR = sizeof("4294967295,")-1 }; char *space = malloc(SIZEOF_INT_AS_STR * n_items); if (space != 0) { size_t i; char *pad = ""; char *dst = space; char *end = space + SIZEOF_INT_AS_STR * n_items; for (i = 0; i < n_items; i++) { snprintf(dst, end - dst, "%s%u", pad, list[i]); pad = ","; dst += strlen(dst); } space = realloc(space, dst - space + 1); } return(space); } 

调用者有责任释放返回的指针 - 并在使用它之前检查它是否为null。 如果分配的金额太大而不值得,那么'realloc()'会释放额外的空间。 这段代码巧妙地假设这些值确实是32位无符号整数; 如果它们可以更大,那么枚举需要适当的调整。

经过测试的代码:

 #include  #include  #include  char *join_int_list(const unsigned int *list, size_t n_items) { enum { SIZEOF_INT_AS_STR = sizeof("4294967295,")-1 }; char *space = malloc(SIZEOF_INT_AS_STR * n_items); if (space != 0) { size_t i; char *pad = ""; char *dst = space; char *end = space + SIZEOF_INT_AS_STR * n_items; for (i = 0; i < n_items; i++) { snprintf(dst, end - dst, "%s%u", pad, list[i]); pad = ","; dst += strlen(dst); } space = realloc(space, dst - space + 1); } return(space); } int main(void) { static unsigned int array[]= { 1, 2, 3, 49, 4294967295U, 0, 332233 }; char *str = join_int_list(array, sizeof(array)/sizeof(array[0])); printf("join: %s\n", str); free(str); return(0); } 

用valgrind检查 - 似乎没问题。


讨论将INT_MAXUINT_MAX转换为字符串:

您可以使用sizeof(“,”STRINGIZE(INT_MAX))而不是硬编码。 stringize宏是一个常见的cpp工具,可以定义为#define STRINGIZE_(v)#v和#define STRINGIZE(v)STRINGIZE_(v)。 - R. Pate

@R Pate:好主意 - 是的,你可以非常有效地做到这一点。 实际上,有两个有趣的想法:使用字符串连接和sizeof()(为了清晰起见需要括号 - 但字符串连接发生得足够早,编译器不担心)以及在INT_MAX上使用字符串化操作。 - 乔纳森莱弗勒

INT_MAX上使用字符串化操作不是一个好主意 - 它只需要是一个“常量表达式”,不一定是数字序列。 它可以定义为((1 << 32)-1),甚至像__int_max这样的东西,只要编译器允许你在任何可以使用常量表达式的地方使用它。 - 咖啡馆

@caf是对的。 考虑以下代码:

 #include  #include  #undef INT_MAX #define INT_MAX (INT_MIN-1 - 100 + 100) #define QUOTER(x) #x #define STRINGIZER(x) QUOTER(x) enum { SIZEOF_INT_AS_STR = sizeof("4294967295,")-1 }; enum { SIZEOF_INT_AS_STR_1 = sizeof(STRINGIZER(INT_MAX) ",")-1 }; int main(void) { printf("size = %d = %d\n", SIZEOF_INT_AS_STR, SIZEOF_INT_AS_STR_1); printf("INT_MAX = %d\n", INT_MAX); printf("UINT_MAX = %u\n", UINT_MAX); return(0); } 

这甚至不能在带有GCC 4.0.1的MacOS X 10.5.8上编译 - 因为未定义标识符INT_MAX 。 未打印INT_MAXUINT_MAX的代码的初步版本有效; 它显示SIZEOF_INT_AS_STR_1的值为31 - 所以@caf是正确的。 添加对INT_MAXUINT_MAX的值的双重检查然后编译失败,这让我感到惊讶。 看一下gcc -E的输出揭示了原因:

 enum { SIZEOF_INT_AS_STR = sizeof("4294967295,")-1 }; enum { SIZEOF_INT_AS_STR_1 = sizeof("((-INT_MAX - 1)-1 - 100 + 100)" ",")-1 }; int main(void) { printf("size = %d = %d\n", SIZEOF_INT_AS_STR, SIZEOF_INT_AS_STR_1); printf("INT_MAX = %d\n", ((-INT_MAX - 1)-1 - 100 + 100)); printf("UINT_MAX = %u\n", (((-INT_MAX - 1)-1 - 100 + 100) * 2U + 1U)); return(0); } 

正如预测的那样, SIZEOF_IN_AS_STR_1的字符串SIZEOF_IN_AS_STR_1不是数字字符串。 预处理器可以评估表达式(尽可能多),但不必生成数字字符串。

INT_MAX的扩展结果是INT_MIN ,而INT_MIN是根据INT_MAX定义的,因此当重写的INT_MAX宏被评估时,“递归扩展”被C预处理器规则阻止。操作, INT_MAX出现在预处理输出中 - 令所有人感到困惑。

因此,有多种原因导致表面上具有吸引力的想法变成一个坏主意。

 unsigned *a; /* your input a[N] */ unsigned i,N; char *b,*m; b=m=malloc(1+N*11); /* for each of N numbers: 10 digits plus comma (plus end of string) */ for (i=0;i0) b[-1]=0; /* delete last trailing comma */ /* now use m */ free(m); 

好吧,对吗? 🙂

 char buf [11 * sizeof (my_list)]; for (int n = 0, int j = 0; j < sizeof (my_list) / sizeof (my_list [0]); ++j) n += sprintf (&buf [n], "%s%u", (j > 0) ? "," : "", my_list [j]); 

你们是否通过线路获得报酬? 🙂


f()声明为char *参数用于原型设计,只需更改char -> int 。 我将这个问题解释为需要一个字符串作为输出,而不仅仅是代码来写入文件。

 #define PRINT(s, l, x, i) snprintf((s), (l), "%s%d", (i) ? ",":"", (x)[i]); char *f(size_t len, char *x) { size_t i, j = 0, k; for(i = 0; i < len; ++i) j += PRINT(NULL, 0, x, i); char *result = malloc(k = ++j); for(*result = i = j = 0; i < len; ++i) j += PRINT(result + j, k - j, x, i); return result; } 

这是一个测试框架:

 #include  #include  #include  // put f() here int main(int ac, char **av) { for(int i = 1; i < ac; ++i) { printf("%s\n", f(strlen(av[i]), av[i])); } return 0; } 
 #include  #include  /* My approach is to count the length of the string required. And do a single alloc. Sure you can allocate more, but I don't know for how long this data will be retained. */ #define LEN(a) (sizeof a / sizeof *a) int main(void) { unsigned a[] = {1, 23, 45, 523, 544}; int i, str_len=0, t_written=0; char tmp[11]; /* enough to fit the biggest unsigned int */ for(i = 0; i < LEN(a); i++) str_len += sprintf(tmp, "%d", a[i]); /* total: we need LEN(a) - 1 more for the ',' and + 1 for '\0' */ str_len += LEN(a); char *str = malloc(str_len); if (!str) return 0; if (LEN(a) > 1) { t_written += sprintf(str+t_written, "%d", a[0]); for(i = 1; i < LEN(a); i++) t_written += sprintf(str+t_written, ",%d", a[i]); } else if (LEN(a) == 1) t_written += sprintf(str+t_written, "%d", a[0]); printf("%s\n", str); free(str); return 0; } 

你们和你们不必要的特殊情况来处理尾随的逗号……只要破坏最后一个逗号然后每次循环运行时进行条件检查便宜。

🙂

 #include  char* toStr(int arr[], unsigned int arrSize, char buff[]) { if (arr && arrSize && buff) { int* currInt = arr; char* currStr = buff; while (currInt < (arr + arrSize)) { currStr += sprintf(currStr, "%d,", *currInt++); } *--currStr = '\0'; } return buff; } int main() { int arr[] = {1234, 421, -125, 15251, 15251, 52}; char buff[1000]; printf("Arr is:%s\n", toStr(arr, 6, buff)); } 

假设buff足够大,将其分配为(最大int + 2的长度)* arrSize)。 灵感来自我的memcpy 🙂

编辑我意识到我之前有一个脑筋,可能只是增加了sprintf的返回值而不是存储临时值。 显然其他答案也这样做,编辑我的答案删除2个不必要的行。

Edit2看起来像争吵的打击我! 他的答案与我的答案非常相似,并且早先提交过。 我谦卑地建议给他+ 1。

假设当你提到“for logging”时你的意思是写入日志文件,那么你的解决方案可能看起来像这样(伪编码):

 for (int x in array) { fprintf(log, "%d", x); if (! last element) fputc(log, ','); } 

就个人而言,为了简单起见,也可能是速度,我会malloc一个大缓冲区,每个元素的大小为“4,294,967,295”和“,”的数组空间。 虽然在创建列表时它不是空间效率高!

然后我将冲刺进入那里,并将“,”附加到所有元素上

最后,我将指针重新分配给没有超出要求的空间。 (size = strlen)

sprintf:成功时,返回写入的字符总数。 此计数不包括自动附加在字符串末尾的附加空字符。

这就是你如何跟踪字符串中strcpy的位置。 🙂

希望有所帮助! 🙂

如果您只想将它​​们打印出来,请参阅其他回复。 (for-loop和printf)

不幸的是总会有三种情况:

  • 空列表(没有逗号,没有项目)
  • 一个项目(没有逗号,一个项目)
  • 两个或更多项目(n-1个逗号,n个项目)

join方法为您隐藏了这种复杂性,这就是它如此美妙的原因。

在C中,我会这样做:

 for (i = 0; i < len; i++) { if (i > 0) /* You do need this separate check, unfortunately. */ output(","); output(item[i]); } 

但是output在哪里,你附加到字符串。 它可以像预先分配的缓冲区上的strcat一样简单,也可以像某个流的printf一样简单(就像我今天在创建一个产生字符串的文件*流中了解到的内存流:-)。

如果您对每次i> = 1的检查感到恼火,您可以这样做:

 if (i > 0) { output(item[0]); for (i = 1; i < len; i++) { output(","); output(item[i]); } } 

如果你想要它的文件,Steven Schlansker的答案很好。

但是,如果你想把它放在一个字符串中,事情会变得更复杂。 你可以使用sprintf ,但是你需要注意不要在字符串中耗尽空间。 如果你有一个C99兼容的snprintf (Linux,BSD,而不是Windows),以下(未经测试,未编译)代码应该工作:

 char *buf = malloc(1024); /* start with 1024 chars */ size_t len = 1024; int pos = 0; int rv; int i; for (i = 0; i < n; i++) { rv = snprintf(buf+pos, len - pos, "%s%d", i = 0 ? "" : ",", my_list[i]); if (rv < len - pos) { /* it fit */ pos += rv; } else { len *= 2; buf = realloc(buf, len); if (!buf) abort(); i--; /* decrement i to repeat the last iteration of the loop */ } } return buf; 

然后呼叫者必须释放buf

 void join(int arr[], int len, char* sep, char* result){ if(len==0){ *result='\0'; } else { itoa(arr[0],result,10); if(len > 1){ strcat(result,sep); join(arr+1,len-1,sep,result+strlen(result)); } } }