【问题标题】:Trying to remove suffix from string with no success尝试从字符串中删除后缀但没有成功
【发布时间】:2021-01-04 14:46:57
【问题描述】:

我正在尝试用下一种方式删除后缀,但最终输出与输入相同


    #include <stdio.h>
    #include <string.h>
    #include <stdlib.h>
    
    #define SUFFIX ".doc"
    
    static void removeSuffix(char **outNewFileName, const char *inFileName)
    {
        *outNewFileName = strdup(inFileName);
        outNewFileName[strlen(inFileName) - strlen(SUFFIX)];
    }

    int main ()
    {
        char *fileName = "tmp.doc";
        char *outnewFileName  = NULL;
        
        removeSuffix(&outnewFileName, fileName);

        free(outnewFileName);
    
        return 0;
    }

例如,如果 fileName 是 tmp.doc outnewFileName 也是 tmp.doc

【问题讨论】:

  • 请不要在发布答案后编辑您的问题。我的回答解决了您的原始问题,而您的编辑看起来好像我没有解决。
  • @user273283 这个语句是什么 outNewFileName[strlen(inFileName) - strlen(SUFFIX)];在做什么?!

标签: c c-strings function-definition suffix


【解决方案1】:

你很亲密!您的 strlen(inFileName) - strlen(SUFFIX) 表达式找到了终止新字符串的正确位置,但您实际上并未对该表达式执行任何操作。

要在该位置终止字符串,请将那里的char 的值设置为(即nul 终止符):

static void removeSuffix(char** outNewFileName, const char* inFileName)
{
    *outNewFileName = strdup(inFileName);
    (*outNewFileName)[strlen(inFileName) - strlen(SUFFIX)] = '\0';
}

在这段代码中,我们使用(*outNewFileName) 来指代新的字符串数组,使用方括号中的偏移量来指代需要更改为nul 的实际字符。 (圆括号是必需的,因为[] 运算符的优先级高于* 运算符。)


注意:正如 cmets 中所指出的,如果您启用了完整的编译器警告,您会看到这一点(来自 clang-cl):

警告:表达式结果未使用 [-Wunused-value]

请随时要求任何进一步的澄清和/或解释。

【讨论】:

  • “tmp.doc”怎么样
  • 我想我们可以提到有问题的语句(具有strlen)将被编译器标记为带有-Wall 选项的警告warning: statement with no effect [-Wunused-value] 以省略@987654332 @
  • @P__J__ 我在 OP 代码中的任何地方都没有看到 "tmp.doc " ......你呢?
  • @AdrianMole 你总是假设理想的情况。那么你就不需要任何错误检查
  • @P__J__ 好的,所以有 许多 种可能的“更好”解决方案来解决这个问题!但是,我的回答专门针对解决 OP 的问题,以及为什么他们的代码不起作用。
【解决方案2】:

对于初学者,总是尝试编写更通用的函数。

这个函数声明

static void removeSuffix(char **outNewFileName, const char *inFileName);

不好。它依赖于魔术字符串文字".doc"。所以你不能使用该功能删除其他后缀。

第二次通过引用传递第一个参数(指针)会产生一个问题,是否应该在分配新地址之前释放指针。

函数定义也没有意义

static void removeSuffix(char **outNewFileName, const char *inFileName)
{
    *outNewFileName = strdup(inFileName);
    outNewFileName[strlen(inFileName) - strlen(SUFFIX)];
}

看来你的意思

static void removeSuffix(char **outNewFileName, const char *inFileName)
{
    *outNewFileName = strdup(inFileName);
    ( *outNewFileName )[strlen(inFileName) - strlen(SUFFIX)] = '\0';
}

但是这种方法是不正确的。传递的字符串可以没有必须删除的后缀。在这种情况下,该函数将返回一个无效字符串。或者传递的字符串可以多次包含与后缀相同的组合。而且用户可以传递一个长度小于后缀长度的字符串。

所以这个函数声明和它的整体定义是错误的和不好的。

该函数可以通过以下方式声明和定义,如下面的演示程序所示。

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

char * removeSuffix( const char *s1, const char *s2 )
{
    char *p = NULL;
    
    if ( *s2 != '\0' )
    {
        for ( char *current = ( char * )s1; ( current = strstr( current, s2 ) ) != NULL; ++current )
        {
            p = current;
        }
    }       
    
    if ( p == NULL || *( p + strlen( s2 ) ) != '\0' ) p = ( char * )( s1 + strlen( s1 ) );
    
    size_t n = p - s1;
        
    p = malloc( n + 1 );
        
    if ( p )
    {
        memcpy( p, s1, n );
        p[n] = '\0';
    }

    return p;
}

int main(void) 
{
    char *fileName = "tmp.doc.doc";
    char *suffix = ".doc";
    
    char *outnewFileName  = removeSuffix( fileName, suffix );
    
    if ( outnewFileName ) puts( outnewFileName );
    
    free( outnewFileName );
    
    return 0;
}

程序输出是

tmp.doc

如您所见,该函数确实从字符串"tmp.doc.doc" 中删除了包含符号".doc" 的两个组合的后缀。

【讨论】:

    【解决方案3】:

    我宁愿:

    char* strrrstr(const char *haystack, const char *needle)
    {
        const char *r = NULL;
        size_t nlen = strlen(needle);
    
        if(haystack && needle)
        {
            while (1) 
            {
                char *p = strstr(haystack, needle);
                if (!p)
                    break;
                r = p;
                haystack = p + nlen;
            }
        }
        return (char *)r;
    }
    
    
    #define SUFFIX ".doc"
    
    static char *removeSuffix(const char *str, const char *suffix)
    {
        char *newString = strdup(str);
        char *pos;
        if(newString)
        {
            pos = strrrstr(newString, suffix);
            if(pos) *pos = 0;
        }
        return newString;
    }
    
    int main ()
    {
        static char *fileName = "tmp.doctmp.doc";
        char *outnewFileName;
        
        outnewFileName = removeSuffix(fileName, SUFFIX);
    
        printf("%s\n", outnewFileName);
    
        free(outnewFileName);
    
        return 0;
    }
    

    您应始终阅读警告并将警告级别设置为尽可能高。如果你这样做了:

    <source>:36:23: warning: statement with no effect [-Wunused-value]
    
       36 |         outNewFileName[strlen(inFileName) - strlen(SUFFIX)];
    
          |         ~~~~~~~~~~~~~~^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
    

    解释问题。

    【讨论】:

      【解决方案4】:

      "...从字符串中删除后缀没有成功"

      如下:

      outNewFileName[strlen(inFileName) - strlen(SUFFIX)];
      

      正在修改的变量需要表示为(*outNewFileName),所以上面改成:

      (*outNewFileName)[strlen(inFileName) - strlen(SUFFIX)];
      

      此外,在这种方法中,可以通过数学运算得出数组索引,但很难正确执行,并且不是从字符串中截取文件扩展名的唯一方法。以下是替代方法,例如,不需要使用 #define 值,并且不需要数组索引计算。 (但确实需要. 文件扩展分隔符)...

      char *new = strdup(filename);//after getting a new string at least as long as original...
      if(new)//use strtok
      {
          char *tok - strtok((*outNewFileName), ".");
          if(tok)
          {
              strcpy((*outNewFileName), tok);//tok contains string w/o extension.
              ...
      

      编辑以解决 cmets:

      另一种比前一种更灵活的方法是创建一种能力,以指定哪个分隔符来标识文件扩展名,并将其作为参数传递以返回字符串的部分直到并包括最后一次出现 /em> 的分隔符。例如:

      strcpy((*outNewFileName), return_base_filename((*outNewFileName), '.'));
      

      其中return_base_filename()定义为:

      //returns string upto last occurance of delimiter.
      char * return_base_filename(char *in, char delimiter)
      {
          char *ptr = in;
          char *ptrKeep = NULL;
          
          while(*ptr)
          {
              if(*ptr == delimiter) 
              {
                  ptrKeep = ptr;
              }
              ptr++;
          }
          in[ptrKeep - in]=0;
          return in;
      }
      

      请注意,此方法适用于以下文件名/目录形式:

      • "base.ext"
      • "base1.base2.base3.ext"
      • "C:\\one\\two\\three\\with.this.file.txt"

      第 3 次需要 2 次调用 'return_base_filename(,)',第一次使用 '\' 作为分隔符,第二次使用 '.'。

      char buf[] = {"C:\\one\\two\\three\\with.this.file.txt"};
      strcpy(buf, return_base_filename(buf,'\\'));
       strcpy(buf, return_base_filename(buf,'.'));
      

      【讨论】:

      • 我不编辑文件名,我正在编辑 outnewFileName
      • OP 的代码是strduping 输入;它不需要编辑原始字符串(API 将其接收为const char*,因此即使main 生成了一个数组而不是指向静态内存的指针,它也无法更改它)。
      • @ryyker:你修复了一些东西,但还不足以真正去除 OP 原始设计中的后缀(它会查找一个值,然后将其丢弃,并且根本不会更改字符串)。 strtok 位至少作为一个替代方案很有趣。
      • strtok 方法不适用于中间包含点的文件名,例如"medical.doctor.doc"
      • 编辑并添加内容以解决前两个 cmets。
      猜你喜欢
      • 1970-01-01
      • 2021-07-22
      • 1970-01-01
      • 2011-05-08
      • 2013-05-10
      • 2011-07-14
      • 1970-01-01
      • 2015-03-27
      相关资源
      最近更新 更多