11.5.5 文本换行
接下来_read要为以文本模式打开的文件转换回车符。在Windows的文本文件中,回车(换行)的存储方式是0x0D(用CR表示),0x0A(用LF表示)这两个字节,以C语言字符串表示则是"\r\n"。而在其他的一些操作系统中,回车的表示却有区别。例如:
Linux/Unix:回车用\n表示。
Mac OS:回车用\r表示。
Windows:回车用\r\n表示。
而在C语言中,回车始终用\n来表示,因此在以文本模式读取文件的时候,不同的操作系统需要将各自的回车符表示转换为C语言的形式。也就是:
Linux/Unix:不做改变。
Mac OS:每遇到\r就将其改为\n。
Windows:将\r\n改为\n。
由于我们所阅读的是Windows的crt代码,所以_read会每遇到一个\r\n就将其改为\n。由于_read处理这一部分的代码很复杂(有近百行),因此这里会提供一个简化的版本来阅读:
if (_osfile(fh) & FTEXT) { if ( (os_read != 0) && (*(char *)buf == LF) ) _osfile(fh) |= FCRLF; else _osfile(fh) &= ~FCRLF; |
首先需要检查文件是否是以文本模式打开,如果不是,就什么也不需要处理。_osfile是一个宏,用于访问一个句柄对应的ioinfo对象的osfile字段(还记得IO初始化时的osfile吗?)。当本次读文件读到的第一个字符是一个LF('\n')时,需要在该句柄的osfile字段中加入FCRLF标记,表明一个\r\n可能跨过了两次读文件。这个标记在一些特殊场合下会有作用(例如ftell函数)。
接下来要进行实际的转换,转换需要经历一个循环:
p = q = buf; while (p < (char *)buf + bytes_read) { 处理p当前指向的字符 p和q后移 } |
p和q一开始指向读取的数据数组的开头,在每一次循环里,进行如下的判断和操作:
(1)*p是CRTL-Z:表明文本已经结束,退出循环。
(2)*p是CR(\r)之外的字符:把p指向的字符复制到q指向的位置,p和q各自后移一个字节 (*q++ = *p++)。
(3)*p是CR(\r)且*(p+1)不是LF(\n):同(2)。
(4)*p是CR(\r)且*(p+1)是LF(\n):p后移2个字节,将q指向的位置写为LR(\n),q后移一个字节(p += 2; *q++ = '\n';)。
p和q一开始始终指向相同的位置,因此情况(2)里的复制实际没有作用,直到p遇到一个\r\n。此时的动作如图11-14所示(以字符串"a\r\nb"为例)。
|
| (点击查看大图)图11-14 换行符转换 |
此时q-buf可得到处理过后的读取字符数。
最后还有一个问题:如果在缓冲的末尾发现了一个CR该怎么办?此时我们无法知道下一个字符是否是LF,所以无法决定是否应该丢弃这个CR字符。这时唯一的办法就是再从文件里读取1个字节,检查它是否是LF;然后再用fseek函数(或具有相同功能的其他函数)把函数指针重新向前移动一个字节。这段操作的伪代码如下:
从文件读1个字节,
如果没有读取成功,那么直接存储CR字符并返回,
如果成功读取了1个字节,那么要考虑下列几种情况:
1. 磁盘文件,且字符不是LF:直接存储CR字符,用seek函数回退文件指针1个字节;
2. 磁盘文件,且字符是LF:丢弃CR字符存储LF字符;
3. 管道或设备文件,且字符是LF:丢弃CR字符存储LF字符;
4. 管道或设备文件,且字符不是LF:存储CR字符,并把LF字节存储在句柄的管道的单字节缓冲(pipech)里。
可以看到在第4种情况里使用了pipech。在之前的部分中我们已经知道这是一个为管道和设备提供的单字节缓冲。由于管道和设备文件不能够使用seek函数回退文件指针,因此一旦读取了多余的一个字符,就必须使用这样的缓冲。由于此处对pipech的赋值将字符LF排除在外,同时此处的赋值是唯一的对pipech有意义的赋值,因此pipech的值永远不会是LF。那么将LF赋值为LF就可以表明该缓冲为空。下面是完整的转换过程代码:
p = q = buf; while (p < (char *)buf + bytes_read) { if (*p == CTRLZ) { /* 遇到文本结束符,退出 */ if ( !(_osfile(fh) & FDEV) ) _osfile(fh) |= FEOFLAG; break; } else if (*p != CR) /* 没有遇到CR,直接复制 */ *q++ = *p++; else { /* 遇到CR,检查下一个字符是否是LF */ if (p < (char *)buf + bytes_read - 1) { /* CR不处于缓冲的末尾 */ if (*(p+1) == LF) { p += 2; *q++ = LF; } else
*q++ = *p++; } else { /* CR处于缓冲的末尾,再读取一个字符 */ ++p; dosretval = 0; if ( !ReadFile( (HANDLE)_osfhnd(fh), &peekchr, 1, (LPDWORD)&os_read, NULL ) ) dosretval = GetLastError(); if (dosretval != 0 || os_read == 0) { *q++ = CR; } else { if (_osfile(fh) & (FDEV|FPIPE)) { /* 管道或设备文件 */ if (peekchr == LF) *q++ = LF; else { /* 如果预读的字符不是LF, 使用pipech存储字符 */ *q++ = CR; _pipech(fh) = peekchr; } } else { /* 普通文件 */ if (q == buf && peekchr == LF) { *q++ = LF; } else { /*如果预读的字符不是LF, 用seek回退文件指针*/ filepos = _lseek_lk(fh, -1, FILE_CURRENT); if (peekchr != LF) *q++ = CR; } } } } } } bytes_read = (int)(q - (char *)buf); }
|
【责任编辑:
云霞 TEL:(010)68476606】