【问题标题】:C - Convert substring to intC - 将子字符串转换为 int
【发布时间】:2016-03-07 09:23:58
【问题描述】:

目前我用 C 语言编写了这个简单的代码,用于将天、分和秒转换为秒:

已编辑(我理解 atoi 的问题,像这样更正了吗?):

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

int getseconds(char * time)
{
    int seconds=0, i=0;
    char buffer[3];
    while (*time != '\0')
    {
        switch (*time)
        {
            case 'h': buffer[i]='\0';i=0;seconds=seconds+atoi(buffer)*3600;break;
            case 'm': buffer[i]='\0';i=0;seconds=seconds+atoi(buffer)*60;break;
            case 's': buffer[i]='\0';i=0;seconds=seconds+atoi(buffer);break;
            case ' ':break;
            default: buffer[i]=*time;i++;break;
        }
        time++;
    }
    return seconds;
}

int main()
{

    char *time = "12h  4m 58s";
    int seconds = getseconds(time);
    printf("%d",seconds);
    return 0;
}

这可以按我的意愿工作,但是没有其他方法可以做到这一点,而无需创建更多变量(例如 C#,我只需要转换“内联”。C 是否只有转换为变量而不是转换为变量的函数? “内联”)?

C# 示例:

        string time = "12h 34m 58s";
        int seconds = int.Parse(time.Substring(0, 2)) * 3600 + int.Parse(time.Substring(4, 2)) * 60 + int.Parse(time.Substring(8, 2));

你可以发现我猜的行数差异:)。

【问题讨论】:

  • 重读问题并删除。
  • 无关注释; buffer 在此代码中不包含字符串(字符串是字符序列,后跟 \0);如果它后面的垃圾恰好是一个数字,那么 atoi 不会返回你所期望的。
  • @immibis 缓冲区只是一个“持有者”,因为特定的“字符串”只有 2 个数字,我不会输出它我真的需要用 \0 终止它吗?
  • atoi 采用 c 风格的字符串。 c 风格的字符串以\0 结尾。所以是的,它是必需的。
  • 内联是什么意思?在一条线上?如果是这样,那么在 C# 中进行了很多类型推断,而您在 C 中无法进行(出于显而易见的原因)。除了 C 字符串不是真正的字符串,而是由 \0 终止的字符数组这一事实之外。 C# 中的 time.Substring 本身可能与您的 getseconds 函数具有相同数量(或更多)的代码。

标签: c type-conversion


【解决方案1】:

您可以使用atoi()strtol() 来完成这项工作。您不需要先提取子字符串,因为这些函数在第一个无法转换的字符处停止。您只需要从不同的角度了解字符串的开始位置。因此,此代码与您的 C# 示例非常相似:

int getseconds(const char * time) {
    return atoi(time) * 3600 + atoi(time + 4) * 60 + atoi(time + 8);
}

当然,它会在一些格式错误的输入上中断,但 C# 也会中断(尽管不是所有的相同输入)。

【讨论】:

  • 嗯,这个例子很有趣,这正是我所需要的。但是在 C# 中只有一个问题,你给出了子字符串的开始和结束,atoi 是如何工作的?简单看下一个char不是数字就返回一个数字?
  • 正如我在回答 atoi() 中所写的那样,当它到达无法转换的字符时停止 - 即不是以 10 为基数的字符。 strtol() 做同样的事情,但有一些额外的功能,例如能够处理 10 以外的基数,并为您提供一种方法来找出它停止解析的位置。
  • 简洁优雅,与C#版本有相同的缺点。 +1
【解决方案2】:

sscanf()"%n" 检测扫描结束接近但确实使用了更多变量。

int getseconds2(const char * time) {
   int h,m,s;
   int n = 0;
   sscanf(time, "%d h%d m%d s %n", &h,&m, &s, &n);
   if (n == 0 || time[n] != '\0') return -1;  // failure
   return h*3600 + m *60 + s;
}

"%n" 保存扫描的字符数。由于在格式末尾使用,因此测试它是否为非零(原始值)以及time 是否以空字符结尾可确保扫描的完整性。

@chqrlie 建议 C# 功能不匹配。

   // to match C#
   if (n == 0) return -1;  // failure

【讨论】:

  • 与 C# 示例不同,如果 time 末尾有多余的字符,您的 sscanf 将失败。
  • @chqrlie 通过删除|| time[n] != '\0' 代码,就可以获得匹配的功能。
  • 虽然这是一个非常好的扫描字符串的方法,例如提供的 OP,但他确实要求一种不涉及任何临时性的方法。
  • @John Bollinger 同意这使用了额外的变量——但它们都存在于函数中,所以 OP 只需要调用int seconds = getseconds2(time);。它归结为不需要额外的变量与所需的错误弹性程度。
  • @chux,同意。另外,我规定避免临时工的要求似乎很武断。
【解决方案3】:

一个更简单的解决方案是可能的:

int getseconds( const char* time)
{
    int h, m, s = 0 ;
    char sdelim = 0 ;
    int check = sscanf( time, "%dh %dm %d%c", &h, &m, &s, &sdelim ) ;
    if( check == 3 && sdelim = 's' )
    {
        s = ((h * 60) + m) * 60 + s ;
    }

    return s ;
}

【讨论】:

  • @chqrlie :在这里编译得很好——你看到了什么错误?为什么它应该返回-1? OP 的解决方案都没有做到这一点。这是设计问题,而不是需求问题。为什么要删除检查变量?那是风格问题。我更喜欢先赋值然后测试而不是调用函数或在 if() 中放置一个复杂的表达式,因为它使调试更容易(在符号调试器中)。
  • @chqrlie :啊 - chux 修复了它,这就是原因。该修复使check 的目的变得清晰。该函数的签名与原始问题中的定义相同,但如果您坚持的话。
  • 感谢@chux 更正编译。 -1 有助于将解析失败与 00h 00m 00s 区分开来,但 OP 似乎并不关心。您可以通过删除 check 变量来进一步简化,但清晰有其优点。
  • John Bollinger 的版本无疑简单得多
  • @chqrlie : 删除 check 并没有简化它,它只是少了一个变量。优化器在任何情况下都会将其删除,并且它的存在使调试变得更简单,如前所述。 - 我会让它站起来谢谢。 Chux 的版本假定字符串不包含以下文本。无论接下来发生什么,此版本都可以使用。
猜你喜欢
  • 1970-01-01
  • 2021-10-29
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多