知无涯之回车换行的故事

2023-10-27 14:20
文章标签 回车 故事 换行 无涯

本文主要是介绍知无涯之回车换行的故事,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

不知各位有没有过这样的经历:

  • Linux上创建的文件在Windows上打开时,结果所有内容会挤成一行。而Windows上创建的文件在Linux上打开时,每一行的结尾又多了一个奇怪字符^M
  • 在安装Windows版的git时,安装向导在某一步会提示你选择”Configuring the line ending conversions”,里面提到了Windows-style和unix-style的line endings,为什么会有这些呢?
  • 调用C语言的API fopen时,会有text mode和binary mode,这两者有什么区别?

其实这一切都和我们常说的回车换行有关,但你有没有很奇怪,什么是回车?直接用换行不就好了,为什么要分开两个词?我们使用的键盘上的键明明起得是换行的作用,为什么叫回车?千万别被绕晕了,本文将和大家讨论有关回车换行的一段有趣的历史,随后将回答这些问题。

目录

  • 历史
    • 打字机
    • 分歧出现
    • 混乱的状况
  • 统一
  • Text Mode VS Binary Mode
    • Windows平台
    • Linux和Mac OSX平台
  • 更多资料
  • 结尾

历史

我们通常所说的回车换行其实只相当于一个概念,即一行结束,开始下一行,英文叫做End-of-Line,简写为EOL。你也可以将这理解为一个逻辑上的换行,但为了与回车换行中的换行区分开来,我们后面还是称呼它为EOL

打字机

回车换行严格说起来是两个独立的概念,即回车和换行,它们的出现要追溯到计算机出现之前,那时有一种电传打字机:Teletype Model 33 ASR,如下图:

Teletype Model 33 ASR

在打字机上,有一个部件叫Carriage,它是打字头,相当于打字机的光标。每输入一个字符,Carriage就前进一格。当输满一行后,想要切换到下一行时,需要Carriage在两个方向上的运动:水平和竖直。水平方向上需要将Carriage移到一行的起始位置,竖直方向上需要纸张向上移动一行,此时也就是相当于Carriage下移了一行。(这在很多影视作品里面可以看到,打字者们打完一行之后,通常会用手拨动一个滑块,然后听到“咔”的一声,接着输入下一行。只是在这款打字机中不再需要人为的去拨动。)而这两个动作分别对应着:

  • Carriage Return(CR),也即回车,它在ASCII表中的值为0x0D,可以用转义符\r表示
  • Line Feed(LF),也即换行,它在ASCII表中的值为0x0A,可以用转义符\n表示

因为打字机是机械的结构,所以虽然从逻辑上只表示为EOF,但从设计上它需要分为两个独立的操作,这也正是我们习惯连起来说回车换行的原因。可以参照下图看看其键盘的布局:

键盘布局

键盘的右方有一个Line FeedReturn,从名字可以看出,这分别对应着前面提到的两个操作。然而,通常一个回车操作不能够在一个字符打印的时间内完成,所以可以利用Carriage移动的时间,去完成另外一个完全独立的操作Line Feed,这也是通常Carriage Return会被放在Line Feed前面的原因。你可以想象,如果在在Carriage和纸移动的过程中按下了其它的字符键,打印的内容将变得十分混乱。所以在Carriage ReturnLine Feed之后,有时会有1~3个NUL字符(即相当于汇编语言中的空指令,仅起占位作用),以等待前两个操作的完成。所以实际上打字机的EOL为:EOL = CR + LF + 1~3NUL

分歧出现

等到早期的计算机发明时,很自然的这两个概念被拿了过来。但是由于那时的存储设备非常昂贵,一些人认为在每行的结尾加两个字符用于换行,实在是极大的浪费,于是各个厂商在这一点上便出现了分歧。

由于一些早期的微型计算机还没有用于隐藏底层硬件细节的设备驱动,所以它们直接沿用了打字机的惯例,使用不带NUL的CRLF作为一个EOL。而CP/M为了和这些微型计算机使用同一个终端,也采用了这种设计。所以它的克隆MS-DOS也同样使用CRLF,由于Windows又是基于MS-DOS,为保持兼容性,所以就导致了如今的Windows是采用CRLF作为EOL,即\r\n(或0x0D 0x0A)。

而Multics在被设计之时就非常认真的考虑了这一问题,设计者们觉得只需一个字符便完全足够来表示EOL,这样更加合理。那么选择CR还是LF呢?本来由于那时的键盘上都有一个Return键,所以可能更好的选择是CR。但当时考虑到CR可以用来重写一行,以完成如粗体和删除线等效果,所以他们选择了稍稍难以理解的LF。然后自己设计了一个设备驱动程序来将LF转换为各种打字机所需要的EOL,这个方案非常完美,当然除了LF稍微奇怪一些。随后一脉相承的Unix和Linux们都继承了这个选择,于是你在这些操作系统上可以发现每一行的结尾是一个LF,即\n(或0x0A)。

Mac系统的选择就更加复杂一些。Apple在设计Mac OS时,他们采用了一个最容易理解的选择:CR,即\r(或0x0D)。但这只维持到Mac OS 9,后一个版本的Mac OSX基于Mach-BSD内核,所以此后版本的Mac OSX在每行的结尾存储了与Linux一样的LF,即\n(或0x0A)。

混乱的状况

还有很多其它的操作系统采用更加不同的方案,这也导致了混乱的产生,文章开始提出的几个问题便由该混乱引起。因为Linux和Mac OSX上使用的是LF,而Windows上使用的是CRLF,那么Linux和Mac OSX上创建的文件在Windows上打开时,由于每一行的结尾只有一个LF,但Windows只认识CRLF,所以便不会有逻辑上的换行处理,故所有的文字被挤到了一行。反过来,如果Windows上的文件在Linux和Mac OSX上打开时,仅需LF便可换行,那么每一行的结尾便多了一个CR,对应的ASCII码为^M

而git的安装向导会特意有一个这样的提醒页面也出于此,因为一个项目可能有多个开发者,每个开发者可能使用的是不同的系统,那么开发者checkout代码时,如果不做换行符的转换,有可能就会出现只有一行或者行尾多了^M的情况。当然,如果你有一个可以识别多种EOL的现代文本编辑器,那么不做转换也无妨(notepad不行)。

如果出现了上面的转换问题时,也别着急,可以对文件进行转换。那在我们写程序时如何正确的处理这些问题?像隐藏硬件细节的驱动程序一样,我们可寄希望于高级语言。

统一

为了避免在这些不同的实现中挣扎,高级语言给我们带来了福音,它们各自使用了统一的方式来处理EOL。在C语言中,你一定知道在字符串中如果要增加一个换行符的话,直接用\n即可,比如:

printf("This is the first line! \nThis is a new line!");

上面的输出将是:

This is the first line!
This is a new line!

为什么C语言选择了\n而不是\r?这绝非偶然。熟悉C语言历史的朋友可能知道当初C语言是Dennis Ritchie为开发Unix而设计,所以它沿用了Unix上EOL的惯例便很容易理解了。而我们知道Unix使用的LF的ASCII码为0x0A,转义符为\n,因此C语言中也使用\n作为换行。

Text Mode VS Binary Mode

但是,千万别简单的认为上面的\n最终写到文件中就一定是其ASCII码0x0A,或者文件中的0x0A被读到内存中就是其转义符\n。这取决于你打开文件的方式。在C语言中,在对文件进行读取操作之前,都需要先打开文件,可以使用下面的函数:

#inlcude <stdio.h>
FILE *fopen(const char *path, const char *mode);

注意看第二个参数mode,它是一个字符指针,通常可以为读(r),写(w),追加(a)或者读写(r+, w+, a+),仅指定这些参数时,文件将被当成是文本文件来操作,即Text Mode,而如果在这些参数之外再指定一个额外的b时,文件便会被当成是二进制文件,即Binary Mode。这两种模式的区别在哪里呢?这里稍稍有些复杂,因为它们在不同的平台上表现不同。

Windows平台

对于Windows平台,因为其使用CRLF来表示EOL,故对于Text Mode需要做一定的转换才能够与C语言保持一致。接下来的两个图可以给出最为直观的描述。

先看二者对于读操作的区别:

读操作

Text Mode下,C语言会尝试去“理解”这些回车与换行,它会知道LFCRLF都可能是EOL,所以不管文件中是LF还是CRLF,被读进内存时都会变成LF。而Binary Mode下,C语言不会做任何的“理解”,所以这些字符在文件中什么样,读到内存中依然那样。

接下来是写操作的区别:

写操作

Text Mode下,内存中的每一个LF写入文件中时都会变为CRLF,当然,如果不幸内存中为CRLF,以此种模式写入到文件中时就会变成CRCRLF注意:这里不是CRLF。原因我想大概是如果你认为内存中的数据是文本,那么它一定是以LF作为EOLCR也一定是你有意而为之,是个有意义的字符,所以它并不会处理。)。而Binary Mode下,内存中的内容会被原封不动的写到文件中。

所以为了保证一致性,一定需要注意配套使用读和写,即读和写采用同一种模式打开文件

Linux和Mac OSX平台

因为Linux和Mac OSX平台与C语言对待EOL的方式完全一致,所以Text ModeBinary Mode在这些平台下没有任何区别,可以参考fopen的man page。实际上,所有遵循POSIX的平台都忽略了b这个参数。

虽说在这些平台上处理EOL非常简单,但是如果你的程序需要移植到其它非POSIX平台上时,请务必正确对待b参数。

结尾

这样一个小小的EOL便如此复杂,给人们带来了极大的困扰,但就如我在知无涯之C++ typename的起源与用法最后讨论过的一样,这个决定是经历过无数决断、波折与妥协才有了现在的结果。你可以选择保守,为向后兼容而作出妥协,那么你得面对不断累加的“不完美”,甚至“丑陋”的设计;你也可大胆尝试,破旧立新,牺牲向后兼容换取进步,那你也许得忍受人们的“唾骂”,或许还需承担被人们抛弃的风险。如何在这之间作出选择,没有明确的答案,恐怕一切就只有靠自己去判断了吧。

(全文完)

这篇关于知无涯之回车换行的故事的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/286226

相关文章

css 处理文本不换行的方法

https://www.cnblogs.com/sensualgirl/p/3712332.html

接下来的这个故事就来自于我的先生,一个交警的口述

这可是没有过的事情。先生是个交通警察,在事故科工作已经五、六年了,对于生离死别、阴阳两隔,用他自己的话说是已经有些麻木了;不用说他,就连我,对那些卷宗里血淋淋的照片都已经有些漠然。他的办公室常有悲悲切切的人来哭诉,他却总能在复议时做到不掺杂感情。我是个爱哭的女人,偏偏先生对于眼泪早已有了职业的免疫力,他说要是每个事故他都要为每个逝者陪眼泪的话,他早就活不下去了,但是今天不同,他分明是掉过泪了。

shell中echo输出换行的方法

echo要支持同C语言一样的\转义功能,只需要加上参数-e,如下所示: [~]#echo "Hello world.\nHello sea"Hello world.\nHello sea[~]#echo -e "Hello world.\nHello sea"Hello world.Hello sea[~]# 不加-e 看一下man的说明: [~]#man echo | cat EC

JD 1204:农夫、羊、菜和狼的故事

OJ题目:click here~~ #define vegetable_go 0#define vegetable_come 1#define sheep_go 2#define sheep_come 3#define wolf_go 4#define wolf_come 5#define nothing_go 6#define nothing_come 7using

2020年数据术语的故事

点击上方蓝色字体,选择“设为星标” 回复”资源“获取更多资源 2020年整个技术圈子要说话题最多的,应该是大数据方向。新感念层出不穷,数据湖概念就是其中之一。这篇文章是关于数据仓库、数据湖、数据集市、数据中台等一些列的概念和发展进程。希望给大家带来一个全面的感知。 本文作者:Murkey学习之旅、开心自由天使 本文整理:大数据技术与架构,未经允许不得转载。 如今,随着诸如互联网以及物联网等

今天做了freemaker 导出word文档 的bug修复,解决 \n换行 问题

结合Freemaker导出文件 public void exportSimpleWord() throws Exception{// 要填充的数据, 注意map的key要和word中${xxx}的xxx一致Map<String,String> dataMap = new HashMap<String,String>();dataMap.put("username", "张三");dataMap.

PMP–一、二、三模–分类–14.敏捷–技巧–故事点

文章目录 技巧一模14.敏捷--术语表-自组织团队--自组织团队是一种跨职能团队,其中为实现团队目标团队成员根据需要轮换着发挥领导作用。 自组织团队的核心就是做什么事情,团队成员说了算。61、 [单选] 作为估算活动持续时间过程的一部分,项目经理促成了与产品负责人和Scrum团队的冲刺计划会议。项目经理将用户故事分解为较小的任务项,以小时为单位估算所需时间,并根据团队的能力确定冲刺待办事项列

vben admin里面换行useTable里面的columns

{title: '标题',dataIndex: 'systemName',width: 300, minWidth: 300,customRender: ({ text }: { text: string }) => {return `${text}`;},},     customRender: ({ text }: { text: string }) => {       retu

王楠首次讲述Cocos Creator背后的故事

Cocos Creator发布至今,得到了许多开发者的支持和喜爱,甚至有小伙伴留言说:幸福来得太突然。水滴石穿,非一日之功。这款工具从诞生到问世究竟经历了怎么样的曲折,未来又会走向何方?这方面,大概没有谁比Cocos Creator制作人王楠更有发言权了。   今天不妨抽出10分钟,听听王楠的讲述,相信或多或少会对你有所启发。   开发Cocos Creator的初衷是什么?   我和几

CSS英文换行

在CSS中,如果你想让英文单词换行,可以使用word-break: break-all;属性。这个属性会强制浏览器在任何字符之间进行换行,即使没有空格或其他分隔符。 没有加样式前的运行结果 CSS样式 p {word-break: break-all;} 运行结果 这将确保段落内的文本在遇到任何字符时都会换行,包括英文单词。