在 C 中,“字符串”只是一个包含字符的内存区域,它以第一个 NUL (0) 字符终止。在这种情况下,字符串所需要的只是指向第一个字符的指针。 (这意味着需要计算字符串的长度,因此请尽量避免不必要地这样做。)
有一些标准库函数可以执行比较字符串和复制字符串等操作,但重要的是要记住字符串的内存管理是您的责任。
虽然对于那些习惯于将字符串作为实际数据类型的语言的人来说,这可能看起来很原始、容易出错并且很复杂,但事实就是如此。如果您打算在 C 中进行字符串操作,则需要习惯它。
不过,只要您遵守规则,C 语言中的字符串操作既高效又轻松。例如,如果要引用从第 3 个字符开始的s 的子字符串,则可以使用指针算法:s + 2。如果您想(临时)在字符串的给定点创建子字符串,您可以在子字符串的末尾将 0 放入字符串中,然后再恢复那里的字符。 (事实上,这就是标准库函数 strtok 所做的,也是使用 (f)lex 构建的词法扫描器的工作方式。)请注意,此策略要求字符数组是可变,所以您将无法将其应用于字符串文字。 (不过,字符串数组很好,因为它们是可变的。)
构建词法扫描器的最佳选择很可能是使用flex。 flex 构建的扫描器将为您做很多事情,包括输入缓冲,并且 flex 允许您指定正则表达式而不是手动编码它们。
但是,如果您想手动完成,这并不难,特别是如果整个输入都在内存中,因此不需要缓冲。 (如果没有标记跨越一行,您也可以一次读取一行输入,但这不如读取固定长度的块高效,这是 flex 扫描器会做的。)
例如,这里是一个简单的扫描器,它处理算术运算符、整数和标识符。它不使用“用 NUL 覆盖”策略,因此可以与字符串文字一起使用。对于标识符,它会创建一个新分配的字符串,因此调用者在不再需要该标识符时需要free。 (没有垃圾收集。C'est la vie。)令牌通过引用参数“返回”;函数的实际返回值是指向源字符串其余部分的指针。省略了很多错误检查。
#include <ctype.h>
#include <stdlib.h>
#include <string.h>
/* The type of a single-character operators is the character, so
* other token types need to start at 256. We use 0 to indicate
* the end of input token type.
*/
enum TokenType { NUMBER = 256, ID };
typedef struct Token {
enum TokenType token_type;
union { /* Anonymous unions are a C11 feature. */
long number; /* Only valid if type is NUMBER */
char* id; /* Only valid if type is ID */
};
} Token;
/* You would normally call this like this:
* do {
* s = next_token(s, &token);
* // Do something with token
* } while (token.token_type);
*/
const char* next_token(const char* input, Token* out) {
/* Skip whitespace */
while (isspace(*input)) ++input;
if (isdigit(*input)) {
char* lim;
out->number = strtol(input, &lim, 10);
out->token_type = NUMBER;
return lim;
} else if (isalpha(*input)) {
const char* lim = input + 1;
/* Find the end of the id */
while (isalnum(*lim)) ++lim;
/* Allocate enough memory to copy the id. We need one extra byte
* for the NUL
*/
size_t len = lim - input;
out->id = malloc(len + 1);
memcpy(out->id, input, len);
out->id[len] = 0; /* NUL-terminate the string */
out->token_type = ID;
return lim;
} else {
out->token_type = *input;
/* If we hit the end of the input string, we don't advance the
* input pointer, to avoid reading random memory.
*/
return *input ? input + 1 : input;
}
}