【发布时间】:2021-12-11 02:13:19
【问题描述】:
我正在阅读 stdin,有时会有 unix 风格的换行符,有时还有 windows 风格的换行符。
如何使用这两种换行符?
【问题讨论】:
-
假设您没有阅读
char的char,我所知道的最优雅的方式在这里描述:stackoverflow.com/a/28462221/694576 可能是重复的?
标签: c
我正在阅读 stdin,有时会有 unix 风格的换行符,有时还有 windows 风格的换行符。
如何使用这两种换行符?
【问题讨论】:
char 的char,我所知道的最优雅的方式在这里描述:stackoverflow.com/a/28462221/694576 可能是重复的?
标签: c
假设你知道会有换行符,解决办法是消耗一个字符,然后决定:
10 - LF ... Unix style newline
13 - CR ... Windows style newline
如果是13,则需要多消耗一个字符(10)
const char x = fgetc(stdin); // Consume LF or CR
if (x == 13) fgetc(stdin); // consume LF
【讨论】:
还有更多newline conventions。特别是,所有四个涉及 CR \r 和 LF \n -- \n、\r、\r\n 和 \n\r -- 实际上是在野外遇到的。
为了读取文本输入,可能是交互式的,并同时支持所有这四种换行编码,我建议使用类似以下的辅助函数:
#include <stdlib.h>
#include <string.h>
#include <locale.h>
#include <ctype.h>
#include <stdio.h>
#include <errno.h>
size_t get_line(char **const lineptr, size_t *const sizeptr, char *const lastptr, FILE *const in)
{
char *line;
size_t size, have;
int c;
if (!lineptr || !sizeptr || !in) {
errno = EINVAL; /* Invalid parameters! */
return 0;
}
if (*lineptr) {
line = *lineptr;
size = *sizeptr;
} else {
line = NULL;
size = 0;
}
have = 0;
if (lastptr) {
if (*lastptr == '\n') {
c = getc(in);
if (c != '\r' && c != EOF)
ungetc(c, in);
} else
if (*lastptr == '\r') {
c = getc(in);
if (c != '\n' && c != EOF)
ungetc(c, in);
}
*lastptr = '\0';
}
while (1) {
if (have + 2 >= size) {
/* Reallocation policy; my personal quirk here.
* You can replace this with e.g. have + 128,
* or (have + 2)*3/2 or whatever you prefer. */
size = (have | 127) + 129;
line = realloc(line, size);
if (!line) {
errno = ENOMEM; /* Out of memory */
return 0;
}
*lineptr = line;
*sizeptr = size;
}
c = getc(in);
if (c == EOF) {
if (lastptr)
*lastptr = '\0';
break;
} else
if (c == '\n') {
if (lastptr)
*lastptr = c;
else {
c = getc(in);
if (c != EOF && c != '\r')
ungetc(c, in);
}
break;
} else
if (c == '\r') {
if (lastptr)
*lastptr = c;
else {
c = getc(in);
if (c != EOF && c != '\n')
ungetc(c, in);
}
break;
}
if (iscntrl(c) && !isspace(c))
continue;
line[have++] = c;
}
if (ferror(in)) {
errno = EIO; /* I/O error */
return 0;
}
line[have] = '\0';
errno = 0; /* No errors, even if have were 0 */
return have;
}
int main(void)
{
char *data = NULL;
size_t size = 0;
size_t len;
char last = '\0';
setlocale(LC_ALL, "");
while (1) {
len = get_line(&data, &size, &last, stdin);
if (errno) {
fprintf(stderr, "Error reading standard input: %s.\n", strerror(errno));
return EXIT_FAILURE;
}
if (!len && feof(stdin))
break;
printf("Read %lu characters: '%s'\n", (unsigned long)len, data);
}
free(data);
data = NULL;
size = 0;
return EXIT_SUCCESS;
}
除了我使用的errno常量(EINVAL、ENOMEM和EIO),上面的代码是C89,应该是可移植的。
get_line() 函数会在必要时动态重新分配行缓冲区以使其足够长。对于交互式输入,您必须在遇到的第一个换行符处接受换行符(因为如果第一个字符恰好是唯一的换行符,则尝试读取第二个字符会阻塞)。如果指定,lastptr 处的单字符状态用于检测和正确处理读取的下一行开始处的任何两字符换行符。如果未指定,该函数将尝试将整个换行符作为当前行的一部分使用(这对于非交互式输入是可以的,尤其是文件)。
换行符不存储或计入行长。为了增加易用性,该函数还跳过非空白控制字符。尤其是嵌入的 nul 字符 (\0) 经常会让人头疼,因此让函数完全跳过这些字符通常是一种稳健的方法。
最后,该函数始终将errno 设置为零——如果没有发生错误,则设置为非零错误代码——包括ferror() 情况,因此检测错误情况是微不足道的。
上面的代码 sn-p 包含一个main(),它读取并显示输入行,使用当前语言环境来表示“非空白控制字符”(!isspace(c) && iscntrl(c))的含义。
虽然这绝对不是读取输入的最快机制,但它并没有那么慢,而且是一种非常健壮的机制。
问题?
【讨论】: