C语言 如何以高效的方式将数字添加到字符串的中间?

htrmnn0y  于 2023-03-01  发布在  其他
关注(0)|答案(2)|浏览(172)

我有这个:

#include <stdio.h>
#include <string.h>

int main(void){
    const char* pFilename = NULL;

    pFilename = "Something.png"

    functionX(pFilename, argX);
}

但是,我想在一个循环中调用该函数,使用不同的文件名,如"Something0.png"、"Something1.png"等
经过一番挖掘,我得出了这个结论:

#include <stdio.h>
#include <string.h>

int main(void){
    const char* pFilename = NULL;
    char buffer[4];
    char nameStd[] = "Something";
    char namePng[] = ".png";
    char nameAll[17];

    pFilename = "Something.png"

    for (i = 0; i < 100; i++) {
        snprintf(buffer, sizeof(buffer), "%d", i);
        strcat(nameAll, pFilename);
        strcat(nameAll, buffer);
        strcat(nameAll, namePng);
        functionX(nameAll, argX);
        memset(nameAll,0,strlen(nameAll));
    }
}

嗯,我不确定这是否可行。而且我现在不能执行代码(因为functionX需要特定的外围设备)。但即使它确实可行,这真的是最节省时间的方法吗?

wqnecbli

wqnecbli1#

组装部件通常要容易得多,可以通过snprintf()(如上所述),也可以自己构建字符串,前缀不会改变,只需要在添加另一个数字时添加后缀,对于1e6字符串,它比snprintf()快60%,但可能不值得这么复杂:

#include <limits.h>
#include <stdint.h>
#include <stdio.h>
#include <string.h>

// A constant expression of log10(INT_MAX) + 1
#define str_uint32_t_len sizeof(uint32_t) * CHAR_BIT * 21306 / 70777 + 2

uint32_t uint32_t_len(uint32_t i) {
    uint32_t n = !i;
    for(uint32_t j = i; j; j /= 10, n++);
    return n;
}

// s is not '\0' terminated
char *uint32_t_to_str(uint32_t u, size_t u_len, char *s) {
    for(char *p = s + u_len; s <= --p; u /= 10)
        *p = '0' + u % 10;
    return s;
}

int main() {
    char prefix[] = "Something";
    char suffix[] = ".png";

    // builds the path template "Something\0.png"
    char path[sizeof prefix + str_uint32_t_len + sizeof suffix - 2] = {0};
    strcpy(path, prefix);
    strcpy(path + sizeof prefix, suffix);

    size_t u_len[] = {1, 1};
    for(uint32_t i = 0; i < 1000000; i++) {
        u_len[1] = uint32_t_len(i);
        uint32_t_to_str(i, u_len[1], path + sizeof prefix - 1);
        if(u_len[0] < u_len[1]) {
            u_len[0] = u_len[1];
            strcpy(path + sizeof prefix + u_len[1] - 1, suffix);
        }
        printf("%s\n", path);
    }
}
flvlnr44

flvlnr442#

在这种情况下,出于性能原因组装零件是没有意义的:用snprintf组成文件名是简单和有效的,并且无论如何比fopen()便宜得多。
以下是一个简化版本:

#include <stdio.h>

int functionX(const char *filename, ...) {
    //printf("%s\n", filename);
}

int main(void) {
    int argX = 1;
    for (int i = 0; i < 100; i++) {
        char filename[32];
        snprintf(filename, sizeof filename, "Something%d.png", i);
        functionX(filename, argX);
    }
    return 0;
}

上面的代码是推荐的方法,对于大多数用途来说足够快了。答案的其余部分只用于教育目的。
如果你真的想压缩开销,你可以尝试直接更新字符串:
下面是一个基准测试示例:

#include <stdio.h>
#include <string.h>
#include <time.h>

void functionX(const char *filename, ...) {
    //printf("%s\n", filename);
}

int s_update(char *s, int len) {
    for (char *p = s + len - 1;;) {
        if (++*p <= '9')
            return len;
        *p = '0';
        if (p == s) {
            memmove(s + 1, s, strlen(s) + 1);
            *s = '1';
            return len + 1;
        }
    }
}

int main(void) {
    char filename[32];
    int argX = 1;
    int repeat = 1000000;
    clock_t c0 = clock();
    for (int i = 0; i < repeat; i++) {
        snprintf(filename, sizeof filename, "Something%d.png", i);
        functionX(filename, argX);
    }
    clock_t c1 = clock();
    strcpy(filename, "Something0.png");
    char *p = strchr(filename, '0');
    int len = 1;
    for (int i = 0; i < repeat; i++) {
        functionX(filename, argX);
        len = s_update(p, len);
    }
    clock_t c2 = clock();

    printf("snprintf: %.3fms\n", (c1 - c0) * 1000.0 / CLOCKS_PER_SEC);
    printf("s_update: %.3fms\n", (c2 - c1) * 1000.0 / CLOCKS_PER_SEC);
    return 0;
}

输出:

snprintf: 81.443ms
s_update: 3.085ms

请注意,在大多数情况下,这种显著的性能改进(27倍)并不能证明这样聪明的代码是合理的:对于你的例子(100次迭代),差别只有30微秒,0,00003秒,几乎不可测量。

相关问题