我在C语言方面的经验比在高级语言方面要差得多。在Cisco,我们使用C,有时我会遇到一些在Java或Python中很容易做到的事情,但在C中很难做到。现在就是其中之一。
我有一个动态分配的无符号整数数组,我需要转换为以逗号分隔的字符串进行日志记录。虽然整数不太可能非常大,但它们在概念上可能是0到4,294,967,295在Python中,这只是一条短线。
my_str = ','.join(my_list)
人们在C中做到这一点有多优雅?我提出了一种方法,但这很糟糕。如果有人知道这样做的好方法,请赐教。
答案 0 :(得分:9)
与其他答案相反,不强制要求C99。
这里真正的问题是不知道你需要的字符串的长度。获取一个数字就像使用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
,但由于我使用的是\0
,我将从之前snprintf
的{{1}}处开始。
使用中:
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
答案 1 :(得分:2)
这个怎么样?
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 <stdio.h>
#include <stdlib.h>
#include <string.h>
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_MAX
或UINT_MAX
转换为字符串:
您可以使用sizeof(“,”STRINGIZE(INT_MAX))而不是硬编码。 stringize宏是一个常见的cpp工具,可以定义为#define STRINGIZE_(v)#v和#define STRINGIZE(v)STRINGIZE_(v)。 - R. Pate
@R Pate:好主意 - 是的,你可以非常有效地做到这一点。实际上,有两个有趣的想法:使用字符串连接和sizeof()(为清晰起见需要括号 - 但字符串连接发生得足够早以至于编译器不担心)以及在{{1上使用字符串化操作}}。 - 乔纳森莱弗勒
在
INT_MAX
上使用字符串化操作不是一个好主意 - 它只需要是一个“常量表达式”,不一定是数字序列。它可以被定义为((1 <&lt; 32)-1,或甚至像__int_max那样令人讨厌,只要编译器允许您在任何可以使用常量表达式的地方使用它。 - 咖啡馆
@caf是对的。请考虑以下代码:
INT_MAX
这甚至不能在带有GCC 4.0.1的MacOS X 10.5.8上编译 - 因为未定义标识符#include <limits.h>
#include <stdio.h>
#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);
}
。未打印INT_MAX
或INT_MAX
的代码的初步版本有效;它表明UINT_MAX
的值为31 - 所以@caf是正确的。添加对SIZEOF_INT_AS_STR_1
和INT_MAX
的值的双重检查然后无法编译,这让我感到惊讶。查看UINT_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
的扩展原来是INT_MAX
,而INT_MIN
又是INT_MIN
的定义,所以当重写时INT_MAX
{1}}宏被评估,C预处理器操作规则阻止了“递归扩展”,并且INT_MAX
出现在预处理输出中 - 引起了所有人的混淆。
因此,有多种原因导致表面上有吸引力的想法变成一个坏主意。
答案 2 :(得分:2)
您实际上可以使用像Glib这样的库,其中包含
等功能gchar * g_strjoin(const gchar * separator, ...);
将多个字符串连接在一起形成一个长字符串,并在每个字符串之间插入可选的分隔符。应使用g_free()释放返回的字符串。
(您仍需要使用g_snprintf()
,可能需要g_printf_string_upper_bound()
以确保空间)
答案 3 :(得分:1)
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]);
答案 4 :(得分:1)
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;i<N;++i)
b+=sprintf(b,"%u,",a[i]);
if (N>0) b[-1]=0; /* delete last trailing comma */
/* now use m */
free(m);
漂亮,对吗? :)
答案 5 :(得分:1)
#include <stdio.h>
#include <stdlib.h>
/* 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;
}
答案 6 :(得分:1)
你们和你的不必要的特殊情况要处理尾随的逗号......只要破坏最后一个逗号然后每次循环运行时进行条件检查便宜。
:)
#include <stdio.h>
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:)
修改强> 我意识到我之前有一个brainfart,可能只是增加了sprintf的返回值,而不是存储temp。显然,其他答案也是如此,编辑我的答案以删除2条不必要的行。
<强> EDIT2 强> 看起来像争吵打败了我!他的回答与我的回答非常相似,并且早先提交过。我谦卑地建议给他+ 1。
答案 7 :(得分:1)
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 <stdlib.h>
#include <stdio.h>
#include <string.h>
// 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;
}
答案 8 :(得分:0)
假设当你提到“for logging”时你的意思是写入日志文件,那么你的解决方案可能看起来像这样(伪编码):
for (int x in array) {
fprintf(log, "%d", x);
if (! last element)
fputc(log, ',');
}
答案 9 :(得分:0)
就个人而言,为了简单起见,也可能是速度,我会使用一个大缓冲区,每个元素的大小为“4,294,967,295”和“,”的数组空间。虽然在创建列表时它不是空间效率!
然后我将冲刺进入那里,并将“,”附加到所有元素
最后,我将指针重新分配给没有超出要求的空间。 (size = strlen)
sprintf:成功时,返回写入的字符总数。此计数不包括自动附加在字符串末尾的附加空字符。
这就是你如何跟踪字符串中strcpy的位置。 :)
希望有所帮助! :)
如果您只想将它们打印出来,请参阅其他回复。 (for-loop和printf)
答案 10 :(得分:0)
不幸的是总会有三种情况:
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
一样简单(就像我今天在Creating a FILE * stream that results in a string中了解到的内存流: - )。
如果您对所有i&gt; = 1的每次检查感到恼火,您可以这样做:
if (i > 0)
{
output(item[0]);
for (i = 1; i < len; i++)
{
output(",");
output(item[i]);
}
}
答案 11 :(得分:0)
如果你想要一个文件,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
。
答案 12 :(得分:0)
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));
}
}
}
答案 13 :(得分:0)
这是一个线性解决方案,它为调用者分配一个呈指数增长的缓冲区(减少对 realloc
的调用,如果重要的话)。包含测试脚本。
#include <stdbool.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
void ensure(bool pred, char *msg, char *file, int line) {
if (!pred) {
fprintf(stderr, "%s:%d: %s", file, line, msg);
exit(1);
}
}
char *arr_to_s(int len, int *arr, char *sep) {
size_t sep_len = strlen(sep);
int result_capacity = 16 + sep_len;
int result_len = 0;
char *result = malloc(result_capacity);
ensure(result, "malloc", __FILE__, __LINE__);
result[0] = '\0';
for (int i = 0; i < len; i++) {
char num[16+sep_len];
int previous_len = result_len;
result_len += sprintf(num, i < len - 1 ? "%d%s" : "%d", arr[i], sep);
if (result_len >= result_capacity) {
result_capacity <<= 1;
result = realloc(result, result_capacity);
ensure(result, "realloc", __FILE__, __LINE__);
}
strcat(result + previous_len, num);
}
return result;
}
void run_basic_tests(void) {
int tests[][4] = {
{0},
{0, 1},
{0, 1, 2},
{0, 42, 2147483647, -2147483648},
};
for (int i = 0; i < 4; i++) {
char *s = arr_to_s(i + 1, tests[i], ", ");
printf("[%s]\n", s);
free(s);
}
}
void run_intensive_tests(int n) {
srand(42);
for (int i = 0; i < n; i++) {
int len = rand() % 2000;
int test[len];
printf("[");
for (int j = 0; j < len; j++) {
test[j] = rand() % 2000000000 - 1000000000;
printf(j < len - 1 ? "%d," : "%d", test[j]);
}
puts("]");
char *s = arr_to_s(len, test, ",");
printf("[%s]\n", s);
free(s);
}
}
int main(void) {
//run_basic_tests();
run_intensive_tests(10000);
return 0;
}
测试运行器:
#!/usr/bin/env bash
gcc -std=c99 -pedantic -Wall \
-Wno-missing-braces -Wextra -Wno-missing-field-initializers -Wformat=2 \
-Wswitch-default -Wswitch-enum -Wcast-align -Wpointer-arith \
-Wbad-function-cast -Wstrict-overflow=5 -Wstrict-prototypes -Winline \
-Wundef -Wnested-externs -Wcast-qual -Wshadow -Wunreachable-code \
-Wlogical-op -Wfloat-equal -Wstrict-aliasing=2 -Wredundant-decls \
-Wold-style-definition -Werror \
-ggdb3 \
-O0 \
-fno-omit-frame-pointer -ffloat-store -fno-common -fstrict-aliasing \
-lm \
-o arr_to_s.out \
arr_to_s.c
./arr_to_s.out > arr_to_s_test_out.txt
cat arr_to_s_test_out.txt | awk 'NR % 2 == 1' > arr_to_s_test_expected.txt
cat arr_to_s_test_out.txt | awk 'NR % 2 == 0' > arr_to_s_test_actual.txt
diff arr_to_s_test_expected.txt arr_to_s_test_actual.txt
瓦尔格林:
==573== HEAP SUMMARY:
==573== in use at exit: 0 bytes in 0 blocks
==573== total heap usage: 103,340 allocs, 103,340 frees, 308,215,716 bytes allocated
==573==
==573== All heap blocks were freed -- no leaks are possible