关于C语言作业中 E-排列问题 的字符输入

提前声明,本题正解应该是字符串读入,在下仅对单个字符读入方式进行分析
文章精髓在于原理部分的引用,若有兴趣,请详读。若嫌长,也有找bug的过程和结论,还有最后的AC代码供参考。

在?找找bug?

本题的题意特别单纯,T组样例,读入一组样例中两行字符串,判断两行字符串能不能相互转化,能就输出Yes,不能就输出No。于是主函数代码框架就可构建如下

scanf("%d", &T);	//读入T
while(T--)
{
	Input();		//读入字符串
    check();		//判断能不能相互转化,并输出
}

判断很简单,统计每一串字符串中每个数字出现的次数,开两个数组a[10]和b[10],输入一个字符ch便在a[ch-‘0’]++,最后在check时从1到9开始遍历,若a[i]!=b[i],则输出No并结束函数,遍历后没有结束check函数就输出Yes
思路很单纯,但是这个题做起来没有想象中那么顺利,关键在于输入字符串的Input这个过程,我先贴一段我在本地编译器AC的输入函数

void Input()
{
    while(scanf("%c", &ch)==1)	//当有字符输入时,继续循环
    {
        if(ch<'0' || ch>'9') break;		//一旦有除数字以外的字符就打破循环,一般来说这个字符会是换行符或者空格
        a[ch-'0']++;
    }
    while(scanf("%c", &ch)==1)
    {
        if(ch<'0' || ch>'9') break;
        b[ch-'0']++;
    }
}

而同样的一段代码在oj上显示的却是wrong answer,如果你打开一个网络编译器(这个上网一搜能找到一堆,我用的是洛谷的IDE),在洛谷的评测机上跑出来也不是想要的答案,经过尝试和修改,正确输入如下

void Input()
{
    ch = getchar();		//在输入字符串前读入一个字符
    while(scanf("%c", &ch)==1)
    {
        if(ch<'0' || ch>'9') break;
        a[ch-'0']++;
    }
    ch = getchar();
    while(scanf("%c", &ch)==1)
    {
        if(ch<'0' || ch>'9') break;
        b[ch-'0']++;
    }
}

同样的,我在读入T后进入循环之前也读入了一次ch=getchar();为什么要多读入一个字符呢??我们来看看在循环中读入这个字符之前和之后是这个全局变量ch是什么吧

void Input()
{
    printf("%d\n", ch);		//插入打印ch的ASCII,换行
    ch = getchar();
    printf("%d\n", ch);		//再打印此时的ASCII,换行
    while(scanf("%c", &ch)==1)
    {
        if(ch<'0' || ch>'9') break;
        a[ch-'0']++;
    }
    ch = getchar();
    while(scanf("%c", &ch)==1)
    {
        if(ch<'0' || ch>'9') break;
        b[ch-'0']++;
    }
}

最终得到的结果如图我们会发现ch的ASCII在一次输入前为13,输入后为10
我们知道二者都不是数字对应的ASCII,在网上查一下ASCII对照表,就知道13对应的是CR(回车键),10对应OA(换行键),而在本地试验ch却没有出现过ch的ASCII为13的情况。

我们可以猜测,网络评测机会多录入一个回车符,这就是网络评测和本地评测的不同。

在?查查原理?

后来问过老师才知道,这是操作系统的问题,在linux和Windows上换行的表示是不一样的,一个是\n,一个是\r\n。并且注意,当输入的字符是\r时,scanf认为是有效输入的(亲测确认),所以需要多读入一次ch。
至于原理,这里有一篇博客讲的很到位

先来段历史

回车”(Carriage Return)和“换行”(Line Feed)这两个概念的来历和区别。
符号 ASCII码 意义
\n 10 换行
\r 13 回车
CR 在计算机还没有出现之前,有一种叫做电传打字机(Teletype Model
33,Linux/Unix下的tty概念也来自于此)的玩意,每秒钟可以打10个字符。但是它有一个问题,就是打完一行换行的时候,要用去0.2秒,正好可以打两个字符。要是在这0.2秒里面,又有新的字符传过来,那么这个字符将丢失。
于是,研制人员想了个办法解决这个问题,就是在每行后面加两个表示结束的字符。一个叫做“回车”,告诉打字机把打印头定位在左边界;另一个叫做“换行”,告诉打字机把纸向下移一行。这就是“换行”和“回车”的来历,从它们的英语名字上也可以看出一二。
后来,计算机发明了,这两个概念也就被般到了计算机上。那时,存储器很贵,一些科学家认为在每行结尾加两个字符太浪费了,加一个就可以。于是,就出现了分歧。

在Windows中: ‘\r’ 回车,回到当前行的行首,而不会换到下一行,如果接着输出的话,本行以前的内容会被逐一覆盖; ‘\n’
换行,换到当前位置的下一行,而不会回到行首;

Unix系统里,每行结尾只有“<换行>”,即"\n";Windows系统里面,每行结尾是“<回车><换行>”,即“\r\n”;Mac系统里,每行结尾是“<回车>”,即"\r";。一个直接后果是,Unix/Mac系统下的文件在Windows里打开的话,所有文字会变成一行;而Windows里的文件在Unix/Mac下打开的话,在每行的结尾可能会多出一个^M符号。

分别在Windws和Linux中查看此文件可知:
Linux中遇到换行符("\n")会进行回车+换行的操作,回车符反而只会作为控制字符("^M")显示,不发生回车的操作。而windows中要回车符+换行符("\r\n")才会回车+换行,缺少一个控制符或者顺序不对都不能正确的另起一行。

c语言编程时(windows系统)\r 就是return 回到 本行 行首 这就会把这一行以前的输出 覆盖掉 如: int main() {
cout << “hahaha” << “\r” << “xixi” ; } 最后只显示 xixi 而 hahaha 被覆盖了 \n
是回车+换行 把光标 先移到 行首 然后换到下一行 也就是 下一行的行首拉 int main() { cout << “hahaha” <<
“\n” << “xixi” ; } 则 显示 hahaha xixi
在windows 系统中,当你输入回车时会自动变成\r\n

在linux下的回车键只代表\n

而在windows下的回车键表示\r\n

\n为进入下一行,\r为打印头回到行首上

linux/unix下只用\n,它就表示回车+换行
而windows下,\r只回车不换行的,\n是换行,但在有些编辑中,单独的\n是不会换行的(如notepad)

一般在程序中,写\n就可以了,它在linux或windows中都能实现回车+换行的功能(只是在文本文件中,linux只会有0x0a,windows会自动换为0x0d
0x0a)

正是因为这样的不同导致了我们代码在OJ上出现wrong anser,改正方案如上,多读入一次ch即可在网络评测时避免CR的影响,但是同样,在本地编译的话则不会得到想要的结果所以说一个备用的在线编译网站是多么重要 不过这样的差异对一般问题没有太大影响,只有当处理字符串时需要注意

在?看看代码?

最后AC代码如下,不同的人有不同的录入思路和判断思路,在下的垃圾代码仅供参考

#include<stdio.h>
#include<string.h>
#define maxn 12

int T, a[maxn], b[maxn];
char ch;

void check()
{
    for(int i=1; i<=9; i++)
        if(a[i]!=b[i])
        {
            printf("No\n");
            return;
        }
    printf("Yes\n");
}

void Input()
{
	ch = getchar();
    while(scanf("%c", &ch)==1)
    {
        if(ch<'0' || ch>'9') break;
        a[ch-'0']++;
    }
    ch = getchar();
    while(scanf("%c", &ch)==1)
    {
        if(ch<'0' || ch>'9') break;
        b[ch-'0']++;
    }
}

int main()
{
    scanf("%d", &T);
    ch = getchar();
    while(T--)
    {
        memset(a, 0, sizeof(a)); 			//记得每次循环数组都要清零
        memset(b, 0, sizeof(b));
        Input();			//读入函数
        check();		//判断并输出
    }
    return 0;
}

/*
输入样例:
4
12345678
85673421
12131416
63421111
46783
60708304
56793200
68579320
*/

然后更新一下字符串做法

#include<stdio.h>
#include<string.h>
#define maxn 105
#define FOR(a, b, c) for(int a=b; a<=c; a++)

int T, a[maxn], b[maxn];
char s1[maxn], s2[maxn];

void check()
{
    //FOR(j, 1, 9) printf("%d ", a[j]); printf("\n");
    //FOR(j, 1, 9) printf("%d ", b[j]); printf("\n");
    FOR(i, 1, 9)
        if(a[i]!=b[i])
        {
            printf("No\n");
            return;
        }
    printf("Yes\n");
}

void Input()
{
    scanf("%s", &s1);
    scanf("%s", &s2);
    int len1 = strlen(s1);
    int len2 = strlen(s2);
    FOR(i, 0, len1-1)
        a[s1[i]-'0']++;
    FOR(i, 0, len2-1)
        b[s2[i]-'0']++;
}

int main()
{
    scanf("%d", &T);
    while(T--)
    {
        memset(a, 0, sizeof(a));
        memset(b, 0, sizeof(b));
        memset(s1, 0, sizeof(s1));
        memset(s2, 0, sizeof(s2));
        Input();
        check();
    }
    return 0;
}

/*
4
12345678
85673421
12131416
63421111
46783
60708304
56793200
68579320
*/

第一次写博客,markdown真好玩,估计之后会玩很久hhhh

猜你喜欢

转载自blog.csdn.net/qq_43455647/article/details/89043011