从学编程的第一天起,我们就接触到了"\r"和"\n"。 "\n" 表示换行(LF, Line Feed),ASCII是0xA。 "\r"表示回车(CR, Carriage Return), ASCII是0xD。 换行是很容易理解的,无需过多的解释。回车是打字机时代的产物,表示回到当前行的最开始。概念本身很简单,如果各个操作系统,框架都严格遵守这个规则,事情就简单多了。

   Unix系列, Apple OS X以及更高的系统,换行是"\n";微软的WIndows以及MS DOS 换行是"\r\n";苹果Macintosh OS 9以及更早, 换行是"\r"。这篇博客我想重点讲讲Windows下的换行。之所以要讨论这个问题,是因为我们在实际项目中遇到了和换行有关的问题,还困扰了我们好几天。

    问题的起因是这样的:我们需要做一个WPF控件,左边ListBox显示测试项,右边的RichTextBox显示所有内容,当选中左边任意测试项,右边RichTextbox会自动跳转到对应的测试项的首行。为了做到这点,我们需要算出所有测试项的总行数。我们最开始的做法是找出"\n"的个数,然后加1即得到总行数。绝大多数情况结果是对的,可是总有些时候,跳转结果是不对的。哪里出错了?

    为了彻底弄清这个问题,我们做了一些实验。首先是控制台程序。 

static void Test()
        {
            string test = "hello1";
            Console.Write(test);
            test = "hello2\r\n";
            Console.Write(test);
            test = "hello3\n";
            Console.Write(test);
            test = "hello4\rworld\n";
            Console.Write(test);
            test = "hello5\r";
            Console.Write(test);

            Console.WriteLine("Press any key to continue...");
            Console.ReadKey();
        }

下面是输出结果:

r语言 代码转行 r语言换行_控件

hello1没有空行,所以hello2紧跟在hello1后面输出;

hello2后面跟\r\n,所以hello3在下一行打印;

同样,hello3后面跟\n,也独占一行;

hello4后面跟着\r,表明,输出hello4后,光标回到改行的第一位,这时再继续输出world会覆盖前面的hello,所以结果是world4;

同理,hello5被后面的Press any key to continue覆盖。


在控制台程序上,一切和我们预想的是一样的。那么为什么在WPF程序上会有出入呢?于是我们又做了一个WPF的demo

public partial class MainWindow : Window
    {
        public MainWindow()
        {
            InitializeComponent();

            string content = "hello1\rhello2\n\rhello3\n\nhello4\r\r\nhello5\n\n\nhello6";

            Paragraph paragraph = new Paragraph(new Run(content));

            this.richtextbox.Document.Blocks.Add(paragraph);
        }
    }

下面是输出结果:

r语言 代码转行 r语言换行_控件_02

这回结果比较有趣了:

hello1后面我们跟的是\r,但实际效果却是换行;

hello2后面跟了\n\r,实际是换了两行;

hello4这个结果最想不到:后面跟了\r\r\n,预想它会换三行,但实际上却只换了两行!

通过更多的实验,我们发现在WPF控件中换行有一下规律:

\n     一行

\r\n   一行

\r\r\n  两行

\r\r     两行

如果需要在控件中计算总行数需要用下面这个公式

"\n" count  - "\r\n" count + "\r" count


我们又检查了当出现错误跳转时的源数据,果然发现有"\r\r\"的情况。 更新了计算公式后,我们的问题解决了。