将标签扩展为C中的空格?

时间:2009-09-20 17:12:16

标签: c tabs

我需要在输入行中展开制表符,以便它们是空格(宽度为8列)。我用之前的代码尝试了它,我用'\ n'替换了大于10个字符的每行中的最后一个空格来创建一个新行。有没有办法在C中制作8个空格以扩展它们?我的意思是我确信它很简单,我似乎无法得到它。

这是我的代码:

int v = 0;
int w = 0;
int tab;
extern char line[];

while (v < length) {

   if(line[v] == '\t')
      tab = v;

   if (w == MAXCHARS) {
      // THIS IS WHERE I GET STUCK
      line[tab] = ' ';
      // set y to 0, so loop starts over
      w = 0;
   }
   ++v;
   ++w;
}

7 个答案:

答案 0 :(得分:4)

这不是关于C语言的问题;这是一个关于找到正确算法的问题 - 您可以在任何语言中使用该算法。

无论如何,如果不重新分配line[]指向更大的缓冲区(除非它是一个很大的固定长度,在这种情况下你需要担心溢出),你根本不能这样做;当你扩展标签时,你需要更多的内存来存储新的更大的线条,所以你试图做的字符替换根本不起作用。

我的建议:我不建议在适当的位置操作(或尝试在内存中操作),我建议将其写为过滤器 - 从stdin读取并一次写入stdout一个字符;这样你就不必担心内存分配或释放或改变行[]的长度。

如果中使用此代码的上下文要求在内存中运行,请考虑实现类似于realloc()的API,其中返回一个新指针;如果您不需要更改正在处理的字符串的长度,您可以简单地保留原始内存区域,但如果您执行需要调整其大小,则该选项可用。

答案 1 :(得分:4)

您需要一个单独的缓冲区来写输出,因为它通常比输入长:

void detab(char* in, char* out, size_t max_len) {
    size_t i = 0;
    while (*in && i < max_len - 1) {
        if (*in == '\t') {
            in++;
            out[i++] = ' ';
            while (i % 8 && i < max_len - 1) {
                out[i++] = ' ';
            }
        } else {
            out[i++] = *in++;
        }
    }

    out[i] = 0;
}

您必须为out预分配足够的空间(在最坏的情况下可能是8 * strlen(in) + 1),而out不能与in相同。

编辑:正如Jonathan Leffler所建议的,max_len参数现在确保我们避免缓冲区溢出。结果字符串将始终以空值终止,即使它被缩短以避免这种溢出。 (我还重命名了该函数,并将int更改为size_t以增加正确性:)。)

答案 2 :(得分:2)

我可能会这样做:

  1. 遍历字符串一次,只计算标签(如果你还不知道字符串的长度)。
  2. 分配original_size + 7 * number_of_tabs个字节的内存(其中original_size计算空字节)。
  3. 再次遍历字符串,将每个非制表符复制到新存储器并为每个制表符插入8个空格。
  4. 如果要在原地进行替换而不是创建新字符串,则必须确保传入的指针指向具有足够内存的位置来存储新字符串(这将更长比原来的因为8个空格或7个字节多于一个标签)。

答案 3 :(得分:2)

这是一个可重入的递归版本,它自动分配一个正确大小的缓冲区:

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

struct state
{
    char *dest;
    const char *src;
    size_t tab_size;
    size_t size;
    _Bool expand;
};

static void recexp(struct state *state, size_t di, size_t si)
{
    size_t start = si;
    size_t pos = si;

    for(; state->src[pos]; ++pos)
    {
        if(state->src[pos] == '\n') start = pos + 1;
        else if(state->src[pos] == '\t')
        {
            size_t str_len = pos - si;
            size_t tab_len = state->tab_size - (pos - start) % state->tab_size;

            recexp(state, di + str_len + tab_len, pos + 1);
            if(state->dest)
            {
                memcpy(state->dest + di, state->src + si, str_len);
                memset(state->dest + di + str_len, ' ', tab_len);
            }

            return;
        }
    }

    state->size = di + pos - si + 1;
    if(state->expand && !state->dest) state->dest = malloc(state->size);
    if(state->dest)
    {
        memcpy(state->dest + di, state->src + si, pos - si);
        state->dest[state->size - 1] = 0;
    }
}

size_t expand_tabs(char **dest, const char *src, size_t tab_size)
{
    struct state state = { dest ? *dest : NULL, src, tab_size, 0, dest };
    recexp(&state, 0, 0);
    if(dest) *dest = state.dest;
    return state.size;
}

int main(void)
{
    char *expansion = NULL; // must be `NULL` for automatic allocation
    size_t size = expand_tabs(&expansion,
        "spam\teggs\tfoo\tbar\nfoobar\tquux", 4);
    printf("expanded size: %lu\n", (unsigned long)size);
    puts(expansion);
}

如果使用expand_tabs()调用dest == NULL,函数将返回扩展字符串的大小,但实际上没有进行扩展;如果dest != NULL但是*dest == NULL,将分配一个正确大小的缓冲区,并且必须由程序员解除分配;如果dest != NULL*dest != NULL,扩展后的字符串将被放入*dest,因此请确保提供的缓冲区足够大。

答案 4 :(得分:1)

未经测试,但这样的事情应该有效:

int v = 0;
int tab;
extern char line[];

while (v < length){
  if (line[v] == '\t') {
    tab = (v % TAB_WIDTH) || TAB_WIDTH;
    /* I'm assuming MAXCHARS is the size of your array. You either need
     * to bail, or resize the array if the expanding the tab would make
     * the string too long. */
    assert((length + tab) < MAXCHARS);
    if (tab != 1) {
      memmove(line + v + tab - 1, line + v, length - v + 1);
    }
    memset(line + v, ' ', tab);
    length += tab - 1;
    v += tab;
  } else {
    ++v;
  }
}

请注意,这是O(n * m),其中n是行大小,m是制表符的数量。这在实践中可能不是问题。

答案 5 :(得分:0)

有多种方法可以将字符串中的制表符转换为1-8个空格。在原位进行扩展的方法效率很低,但处理它的最简单方法是使用一个函数来获取输入字符串和一个足够大的扩展字符串输出缓冲区。如果输入是6个制表符加上X和换行符(8个字符+终止空值),则输出将是48个空格,X和换行符(50个字符+终止空值) - 因此您可能需要比输出缓冲区大得多的输出缓冲区。输入缓冲区。

#include <stddef.h>
#include <assert.h>

static int detab(const char *str, char *buffer, size_t buflen)
{
    char *end = buffer + buflen;
    char *dst = buffer;
    const char *src = str;
    char c;

    assert(buflen > 0);
    while ((c = *src++) != '\0' && dst < end)
    {
         if (c != '\t')
             *dst++ = c;
         else
         {
             do
             {
                 *dst++ = ' ';
             } while (dst < end && (dst - buffer) % 8 != 0);
         }
    }
    if (dst < end)
    {
        *dst = '\0';
        return(dst - buffer);
    }
    else
        return -1;
}

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

#ifndef TEST_INPUT_BUFFERSIZE
#define TEST_INPUT_BUFFERSIZE 4096
#endif /* TEST_INPUT_BUFFERSIZE */
#ifndef TEST_OUTPUT_BUFFERSIZE
#define TEST_OUTPUT_BUFFERSIZE (8 * TEST_INPUT_BUFFERSIZE)
#endif /* TEST_OUTPUT_BUFFERSIZE */

int main(void)
{
     char ibuff[TEST_INPUT_BUFFERSIZE];
     char obuff[TEST_OUTPUT_BUFFERSIZE];

     while (fgets(ibuff, sizeof(ibuff), stdin) != 0)
     {
          if (detab(ibuff, obuff, sizeof(obuff)) >= 0)
              fputs(obuff, stdout);
          else
              fprintf(stderr, "Failed to detab input line: <<%.*s>>\n",
                      (int)(strlen(ibuff) - 1), ibuff);
     }
     return(0);
 }
 #endif /* TEST */

此测试的最大问题是很难证明它正确处理输出缓冲区中的溢出。这就是为什么缓冲区大小有两个'#define'序列 - 实际工作的默认值非常大,压力测试的缓冲区大小可独立配置。如果源文件是dt.c,请使用如下编译:

 make CFLAGS="-DTEST -DTEST_INPUT_BUFFERSIZE=32 -DTEST_OUTPUT_BUFFERSIZE=32" dt

如果要在此文件之外使用'detab()'函数,您将创建一个包含其声明的标头,并且您将在此代码中包含该标头,并且该函数不会是静态的,当然。

答案 6 :(得分:0)

这是malloc(3)一个更大的正确大小的缓冲区并返回扩展字符串的缓冲区。它没有除法或模数运算。它甚至还带有一个测试驱动程序。如果使用gcc,则使用-Wall -Wno-parentheses安全。

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

static char *expand_tabs(const char *s) {
  int i, j, extra_space;
  char *r, *result = NULL;

  for(i = 0; i < 2; ++i) {
    for(j = extra_space = 0; s[j]; ++j) {
      if (s[j] == '\t') {
        int es0 = 8 - (j + extra_space & 7);
        if (result != NULL) {
          strncpy(r, "        ", es0);
          r += es0;
        }
        extra_space += es0 - 1;
      } else if (result != NULL)
        *r++ = s[j];
    }
    if (result == NULL)
      if ((r = result = malloc(j + extra_space + 1)) == NULL)
        return NULL;
  }
  *r = 0;
  return result;
}

#include <stdio.h>

int main(int ac, char **av) {
  char space[1000];
  while (fgets(space, sizeof space, stdin) != NULL) {
    char *s = expand_tabs(space);
    fputs(s, stdout);
    free(s);
  }
  return 0;
}