C语言中的指针和字符串解析

5

我想知道指针和字符串解析是如何工作的,请帮忙解释一下。我知道我可以在循环中执行以下操作,但我仍然不太明白它的工作原理。

  for (a = str;  * a;  a++) ...

例如,我正在尝试从字符串中获取最后一个整数。如果我有一个字符串 const char *str = "some string here 100 2000";,那么如何解析它并获取字符串的最后一个整数(2000),知道最后一个���数(2000)可能会变化。
谢谢。

谢谢大家!现在一切都变得更加清晰了。 - Robert74
5个回答

8

for (a = str; * a; a++) ...

这段代码会将指针 a 初始化为字符串的开始位置,然后在每一步中将 a 解引用直到被隐式转换为假值(false),最后每次循环都将 a 递增。

基本上,你会遍历整个数组直到字符串末尾的 NUL 终止符(\0),因为 NUL 终止符会被隐式转换为 false,而其他字符则不会。

通过以上的方法,我怎么能解析它并获取字符串中的最后一个整数(2000),知道该整数的位置可能会变化。

你需要寻找 \0 前的最后一个空格,然后调用一个函数将剩余的字符转换为整数,具体可以使用 strtol 函数。

考虑使用以下方法:

  • 查找字符串的结尾(使用上述循环)
  • 向后查找最后一个空格。
  • 使用空格位置调用 strtol

-

for (a = str; *a; a++);  // Find the end.
while (*a != ' ') a--;   // Move back to the space.
a++;  // Move one past the space.
int result = strtol(a, NULL, 10);

或者,你也可以仅仅追踪最后一个标记的开始位置:
const char* start = str;
for (a = str; *a; a++) {     // Until you hit the end of the string.
  if (*a == ' ') start = a;  // New token, reassign start.
}
int result = strtol(start, NULL, 10);

这个版本的好处是不需要在字符串中加空格。


如果字符串中不包含空格,那么这段代码就会出错。它将循环超过字符串的开头,可能进入无效的地址,导致程序崩溃。 - R.. GitHub STOP HELPING ICE
如果字符串正确地以 null 结尾,则 a = str + strlen(str) 指向字符串的最后一个字节(null 字节)之后的字节;这与 for 循环几乎相同,但更易读,我认为;此外,您可以使用 isspace 而不是 *a != ' ' - ShinTakezou
@R..:没错,但考虑到问题的措辞,我认为这是一个安全的假设。 - Stephen
@ShinTakezou: 也是没错的 :) 我考虑过使用 strlen,但是OP说“使用上述方法”,所以我就这样做了... 另一方面,isspace 可能更清晰。 - Stephen
@R..:谢谢你的建议,我已经添加了一个不需要空格的版本。 - Stephen
@Stephen:看一下这个链接https://dev59.com/aE7Sa4cB1Zd3GeqP5rPk#3129035,它使用了`strrchr()`而不是自己写循环。 - SiegeX

3

您只需要实现一个简单的状态机,它有两个状态,例如

#include <ctype.h>

int num = 0; // the final int value will be contained here
int state = 0; // state == 0 == not parsing int, state == 1 == parsing int

for (i = 0; i < strlen(s); ++i)
{
    if (state == 0) // if currently in state 0, i.e. not parsing int
    {
        if (isdigit(s[i])) // if we just found the first digit character of an int
        {
            num = s[i] - '0'; // discard any old int value and start accumulating new value
            state = 1; // we are now in state 1
        }
        // otherwise do nothing and remain in state 0
    }
    else // currently in state 1, i.e. parsing int
    {
        if (isdigit(s[i])) // if this is another digit character
        {
            num = num * 10 + s[i] - '0'; // continue accumulating int
            // remain in state 1...
        }
        else // no longer parsing int
        {
            state = 0; // return to state 0
        }
    }
}

4
这需要3行代码,只需一次解析而非分析每个字符。 - Stephen
1
这是一种效率低下的方法;它解析所有字符串并丢弃除最后一个之外的所有内容。你应该只调用一次strlen()并将其保存在temp变量中,而不是像此代码一样在每次迭代时调用它(如果字符串是const char *,编译器可能会为你优化)。 - Tim Schaeffer
@Paul R:我对这个术语非常熟悉。 :) 我的“yuck”并不是指效率低,而是指你的解决方案过于复杂。看看有多少分支-那通常是错误隐藏的地方,看看你需要多少注释来解释它!这是一个聪明的解决方案,但对于这个问题来说,实在是过度了。 - Stephen
@Paul R,我没有点踩。除非真的很糟糕,否则我从不点踩。看到C99被进一步推广,这让我感到难过。 :) 我不喜欢C99,即使注释样式是支持的,它也几乎没有得到完全支持。我更喜欢严格的ANSI C,尽管在某些情况下(内核编程)我不得不放弃它。此外,我不确定gcc是否缓存strlen的结果,有关此事的任何文档吗? - BobbyShaftoe
1
好的,这是一个点赞。我认为这是一个可以的答案。缓存方面很有趣。 - BobbyShaftoe
显示剩余5条评论

3

我知道这个问题已经有答案了,但到目前为止所有的答案都在重新创建标准C库中可用的代码。这是我会使用的方法,利用strrchr()

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

int main(void)
{

    const char* input = "some string here 100 2000";
    char* p;
    long l = 0;

    if(p = strrchr(input, ' '))
        l = strtol(p+1, NULL, 10);

    printf("%ld\n", l);

    return 0;
}

输出

2000

0
  for (a = str;  * a;  a++)...

等同于

  a=str;
  while(*a!='\0') //'\0' is NUL, don't confuse it with NULL which is a macro
  {
      ....
      a++;
  }

-1
你所展示的循环只是遍历了所有字符(字符串是指向以0结尾的1字节字符数组的指针)。为了解析,你应该使用sscanf或更好的C++的字符串和字符串流。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接