【问题标题】:Prepending to a string前置字符串
【发布时间】:2010-02-24 17:45:03
【问题描述】:

使用尽可能少的内存在 C 字符串前添加最有效的方法是什么?

我正在尝试重建大型目录树中文件的路径。

这是我之前在做什么的一个想法:

char temp[LENGTH], file[LENGTH];
file = some_file_name;

while (some_condition) {
    parent_dir = some_calculation_that_yields_name_of_parent_dir;
    sprintf(temp, "%s/%s", parent_dir, file);
    strcpy(file, temp);
}

这似乎有点笨拙。

任何帮助将不胜感激。谢谢!

【问题讨论】:

  • 你说大尺度,这让我相信你需要它是快速的。在您的情况下哪个更重要?快还是小?
  • 我想说快速/高效肯定更重要。
  • @d03boy:哎呀,忘了那个。谢谢。
  • some_calculation_that_yields_name_of_parent_dir 能否以某种方式不仅返回父目录,还返回 parent_dir 的长度? (这将在下面发布的某些算法中节省一个 strlen 并允许您最后只进行一次分配。)

标签: c string concatenation


【解决方案1】:

如果你想在同一个内存块中复制,就很难避免。如果分配的块足够大,您可以使用memmove 将原始字符串移动您想要添加的长度,然后将其复制到开头,但我怀疑这会更少“笨重”。但是,它会为您节省额外的内存(同样,假设原始块有足够的可用空间供它们使用)。

类似这样的:

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

void prepend(char* s, const char* t);

/* Prepends t into s. Assumes s has enough space allocated
** for the combined string.
*/
void prepend(char* s, const char* t)
{
    size_t len = strlen(t);
    memmove(s + len, s, strlen(s) + 1);
    memcpy(s, t, len);
}

int main()
{
    char* s = malloc(100);
    strcpy(s, "file");
    prepend(s, "dir/");

    printf("%s\n", s);
    return 0;
}

【讨论】:

  • 这是有道理的。除了 for 循环,你不能使用 memcpy 吗?
  • prepend() 可以通过在 s 和 t 上使用 C99“限制”限定符来进行更多优化。
  • 如果st 长,它将被切断。 memmove 中的第三个参数应考虑s 的长度。
  • 应该是:memmove(s + len, s, strlen(s));您想将 strlen(s) 字节从源复制到目标。
  • @yentup, @brooksbp: 改为移动 strlen(s)+1 字节 - 尾随 0 也必须移动。
【解决方案2】:

如果您不需要将字符串按顺序存储,而只是显示按顺序排列,那么请使用一种称为“绳索”的东西。 (它由许多“字符串”组成,请参阅。)

我相信它基本上是struct { char *begin; char *end }; 的向量(用 C 术语来说,是一个数组)

在 C++ 中,它实现了所有的 std::string 函数。在 C 中,您需要为所有 strxxx() 函数编写(或获取一个库)替换函数。

“绳子”将一个字符串添加到另一个字符串之前所做的只是插入一个新的开始,结束对指向新的字符串。如果它是一个临时指针,它可能还必须复制新的字符串。或者,如果它是一个分配的字符串,它可以只获得该字符串的所有权。

绳子非常适合大弦。但是使用 memmove 和 memcpy 处理大约 8 KB 以下的内容会更快。

【讨论】:

  • +1 我喜欢这个建议。即使最终需要的是一个字符数组,if 最后也很容易计算绳索的最终长度并分配一个大小合适的缓冲区(并复制一次)。跨度>
【解决方案3】:

sprintf() 通常不是“快”。既然你知道它预先挂起 memmove() 两次可能会更好地提高速度。

如果您最初使用 malloc() 分配字符串,您可能会考虑使用 realloc() 调整字符数组的大小,以便它们可以包含新字符串。

   char* p = malloc( size_of_first_string );
   ...
   p = realloc( p, size_of_first_string + size_of_prepended_string + 1 );
   memmove( p + size_of_prepended_string, p, size_of_first_string );
   memmove( p, prepended_string, size_of_prepended_string );

【讨论】:

  • 如果您知道源和目标不重叠,memcpy() 通常会快得多。因此,如果它返回不同的指针,可能值得检查 realloc 返回并使用 memcpy。
  • 您不需要在 realloc 中 + 1 来说明终止 \0 字符?
  • @Geremia 是正确的。不要忘记在 realloc 的大小上加一。我会编辑它以插入它
【解决方案4】:

也许我很困惑,但我相信前置与交换字符串的附加相同。因此,可以将字符串“World”追加到“Hello”,而不是“Hello”添加到“World”:

const char world[] = "World";
const char hello[] = "Hello";

// Prepend hello to world:
const unsigned int RESULT_SIZE = sizeof(world) + sizeof(hello) + 2 * sizeof('\0');
char * result = malloc(RESULT_SIZE);
if (result)
{
  strcpy(result, hello);
  strcat(result, world);
  puts("Result of prepending hello to world: ");
  puts(result);
  puts("\n");
}

此外,执行时间的主要浪费是查找字符串的结尾。如果将字符串与长度一起存储,则可以更快地计算结尾。

【讨论】:

  • 你的想法是对的,但是在这种情况下它并不能很好地工作,因为有两个以上的字符串。我需要迭代地添加到一个字符串。如果您将 strcpy 和 strcat 行放在 while 循环中,则在第二次迭代中,您必须创建一个新的 char 数组或移动存储在“结果”中的内容,以便您有空间进行前置。
【解决方案5】:

您可以爬到目录树的顶部保留名称,然后一次将所有名称粘贴在一起。至少你不会因为推到前面而做不必要的复制。

int i = 0;
int j;

char temp*[MAX_DIR_DEPTH], file[LENGTH];

while (some_condition) {
    temp[i++] = some_calculation_that_yields_name_of_parent_dir;        
}

char *pCurrent = file;    
for( j = i-1; j > 0; j-- )
{
    strcpy(pCurrent, temp[j]);
    pCurrent += strlen(temp[j]);
    *pCurrent++ = '\';
}
strcpy(pCurrent, filename);
*pCurrent = 0;

【讨论】:

    【解决方案6】:

    您可以从末尾开始维护字符串。既然你似乎已经知道 maxSize...

    所以基本上如果文件最初是 (foo.txt)

    [] [] [] [] [] [f] [o] [o] [.] [t] [x] [t] [\0]
                 ^
                 |
              lastEmpty           
    

    现在如果你添加一个父目录 a/ 它看起来像

    [] [] [] [a] [/] [f] [o] [o] [.] [t] [x] [t] [\0]
           ^      
           |      
        lastEmpty           
    

    所以代码看起来像(可能有错误,但你明白了)。

    char temp[LENGTH], file[LENGTH]; 
    int lastEmpty = put_at_end(some_file_name, file);  
    // lastEmpty points to right most empty slot
    
    while (some_condition) { 
        parent_dir = some_calculation_that_yields_name_of_parent_dir; 
    
        int len = strlen(parent_dir);
        char *tmp = parent_dir + len -1;
    
        while (lastEmpty > 0) {
            file[lastEmpty] = *tmp;
            lastEmpty --;
            tmp--;
        }
    } 
    

    因为我想我们可以期望 parent_dir 很小,所以检查两次应该没问题。如果你想传递文件字符串,你可以使用file+lastEmpty+1

    【讨论】:

      【解决方案7】:

      此解决方案没有多余的复制。它确实需要一个 strlen,因此如果目录名称检索可以返回复制的字节数,或者如果您可以预先计算父目录字符串长度,则可以对其进行优化。

      void GetFilename(char *pFile)
      {
          strcpy(pFile, "someFile");
      }
      
      void GetParentDir(char *pDir)
      {
          strcpy(pDir, "/parentdir");
      }
      
      int _tmain(int argc, _TCHAR* argv[])
      {
      
          char path[1024];
          GetParentDir(path);
          int dirSize = strlen(path);
          path[dirSize] = '/';
          GetFilename(path + dirSize + 1);
          printf(path);
          return 0;
      }
      

      【讨论】:

        【解决方案8】:

        我在数组的左侧和右侧留下一个缓冲区。你必须持有两个索引,但如果你必须做很多次(否则效率不会有问题),它会起作用。我建议的两个索引是]s;e],一个包含一个不包含:

         #define BUFSIZE 256
         #define LEFTBUF 20
         struct mstring
         {
           char * string;
           unsigned s;
           unsigned e;
          }
          void checkbuf(struct mstring *value, int newstringlen, char   leftorright)
          {
          //have fun here
          }
          char * concat (struct mstring * value, char * str)
          {
               checkbuf(value, strlen(value,str), 'r');
               int i=0;
               while (str[i])
                    value->string[value->e++]=str[i++];
           }
           char * set(struct mstring * value, char * str)
           {
                value->e=LEFTBUF;
                value->s=LEFTBUF;
                concat( value,str);
        
           }
        
          char * prepend (struct mstring * value, char * str)
          {
               checkbuf(value, strlen(value,str), 'l');
               int i=strlen(value,str)-1;
               while (i>=0)
                    value->string[--value->s]=str[i--];
           }
          int main()
          {
              struct mstring * mystring= (struct mstring *) malloc(sizeof(struct mstring) );
              mystring->string=(char*)malloc(sizeof(char)*BUFSIZE);
              set( mystring,"World");
              prepend(mystring,"Hallo")
        
          }
        

        那么你必须准备一个用于填充子字符串的函数......

        【讨论】:

          猜你喜欢
          • 1970-01-01
          • 1970-01-01
          • 2011-01-02
          • 2015-01-13
          • 1970-01-01
          • 2011-05-15
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          相关资源
          最近更新 更多