输入与输出

一、标准输入流

1. C标准输入

C语言使用标准输入输出函数,需要包含头文件。而在 C++ 中,只要包含头文件,就完全可以使用这些 C 中的输入输出函数。

标准输入流对缓冲区的理解

stdin是一个文件描述符(Linux)或句柄(Windows),它在 C 程序启动时就被默认分配好。在 Linux 中一切皆文件,stdin也相当于一个可读文件,它对应着键盘设备的输入。因为它不断地被输入,又不断地被读取,像流水一样,因此通常称作输入流。

stdin是一种行缓冲I/O。当在键盘上键入字符时,它们首先被存放在键盘设备自身的缓存中(属于键盘硬件设备的一部分)。只有输入换行符时,操作系统才会进行同步,将键盘缓存中的数据读入到stdin的输入缓冲区(存在于内存中)。所有从stdin读取数据的输入流,都是从内存中的输入缓冲区读入数据。当输入缓冲区为空时,函数将被阻塞。

若无特殊说明,以下所有的**“缓冲区”**均是指内存中的stdin输入缓冲区。用户程序中自定义的buffer数组、str数组等,将称作“数组”、“变量”,以免产生混淆。

scanf()

按照特定格式从stdin读取输入。

scanf(格式控制符,地址列表);	
用法示例
char str[100];
int a;
scanf("%s %d", str, &a);    // 注意,传入的一定是变量的地址
对空白字符的处理

1.缓冲区开头:丢弃空白字符(包括空格、Tab、换行符),直到第一个非空白字符才认为是第一个数据的开始。

2.缓冲区中间:开始读取第一个数据后,一旦遇到空白字符(非换行符), 就认为读取完毕一次。遇到的空白字符残留在缓冲区,直到下一次被读取或刷新。例如输入字符串this is test,则会被认为是3个字符串。

3.缓冲区末尾:按下回车键时,换行符\n残留在缓冲区。换行符之前的空格可以认为是中间的空白字符,处理同上。
注意,格式控制符只会读取正确类型的变量。如果输入格式不正确,比如在%d处输入了一个字符a,则会使读取中断,即后续不读取任何变量。

格式控制符说明
类型 类型输入 参数类型
%d 十进制整数 int *
%u 无符号十进制整数 unsigned int *
%o 八进制整数 int *
%x 十六进制整数 int *
%f %e %g 浮点数 float *
%lf %le %lg 双精度浮点数 double *
%c 单个字符(含空白字符) char *
%s 字符串 char *(字符数组的地址)
%lld 长整型 long long *
%Lf 更高精度浮点数 long double *

注意,%c是一个比较特殊的格式符号,它将会读取所有空白字符,包括缓冲区开头的空格、Tab、换行符,使用时要特别注意。

scanf()的读取也没有边界,所以并不安全。C11 标准提供了安全输入scanf_s()

scanf()对应的输出函数是printf()

强制输入格式

1.如果格式控制串中有非格式字符则输入时也要输入该非格式字符。

scanf("%d,%d,%d",&a,&b,&c);

其中用非格式符“ , ”作间隔符,故输入时应为:5,6,7。又如:

scanf("a=%d,b=%d,c=%d",&a,&b,&c);

则输入应为:a=5,b=6,c=7。

2.控制长度

用十进制整数指定输入的宽度(即字符数)。

例如(数据截取):

scanf("%5d",&a);

输入12345678只把12345赋予变量a,其余部分被截去。

又如(数据切分):

scanf("%4d%4d",&a,&b);

输入12345678将把1234赋予a,而把5678赋予b。

注意,scanf函数中没有精度控制,如:scanf("%5.2f",&a);是非法的。不能企图用此语句输入小数为2位的实数。

gets() - 不建议

按下回车键时,从stdin读取一行

用法示例
char str[100];
gets(str);
对空白字符的处理

1.所有空格、Tab等空白字符均被读取,不忽略。

2.按下回车键时,缓冲区末尾的换行符被丢弃,字符串末尾没有换行符\n,缓冲区也没有残留的换行符\n

注意,gets()不能指定读取上限,因此容易发生数组边界溢出,造成内存不安全。C11 使用了gets_s()代替gets(),但有时编译器未必支持,因此总体来说不建议使用gets()函数来读取输入。

gets()对应的输出函数是puts()

fgets()

从指定输入流读取一行,输入可以是stdin,也可以是文件流,使用时需要显式指定。

读取文件流示例
char str[100];
memset(str, 0, sizeof(str));
int i = 1;

FILE *fp = fopen("...test.txt", "r");
if (fp == NULL) {
    printf("File open Error!\n");
    exit(1);
}

while (fgets(str, sizeof(str), fp) != NULL)
    printf("line%d [len %d]: %s", i++, strlen(str), str);

fclose(fp);
读取stdin示例
char str[100];
memset(str, 0, sizeof(str));
int i = 1;
while (fgets(str, sizeof(str), stdin) != NULL)
    printf("line%d [len %d]: %s", i++, strlen(str), str);
对空白字符的处理

1.所有空格、Tab等空白字符均被读取,不忽略。

2.按下回车键时,缓冲区末尾的换行符也被读取,字符串末尾将有一个换行符\n。例如,输入字符串hello,再按下回车,则读到的字符串长度为6。

fgets()函数会自动在字符串末尾加上\0结束符。

第 2 个参数n指定了读取的最大长度。函数读到n-1个字符(包括换行符\n)就会停止,并在末尾加上\0结束符。剩余字符将残留在缓冲区。

建议使用fgets()完全替代gets()

fgets()对应的输出函数是fputs()

getchar()

stdin读取一个字符。

getchar()实际上也由fgetc()宏定义而来,只是默认输入流为stdin

用法示例
char a;
a = getchar();
对空白字符的处理

1.所有空格、Tab、换行等空白字符,无论在缓冲区开头、中间还是结尾,均会被读取,不忽略。

2.因为只读取一个字符,所以如果输入多于1个字符(包括换行符),则它们均会残留在缓冲区。具体地说,如果什么字符都不输入,直接按下回车键,则读取到的是换行符\n,缓冲区无任何残留;如果输入一个字符如a,然后按下回车键,则读取到的是字符a,同时换行符\n残留在缓冲区。

getchar()常常用于清理缓冲区开头残留的换行符。当知道缓冲区开头有\n残留时,可以调用getchar()但不赋值给任何变量,即可实现冲刷掉\n的效果。

getchar()对应的输出函数是putchar()

gerchar()是快读的模板基础。

2. C++标准输入

C++中使用标准输入输出需要包含头文件。一般使用iostream类进行流操作,其封装很完善,也比较复杂,本文只介绍一部分。

cin

cin是 C++ 的标准输入流对象,即istream类的一个对象实例。cin有自己的缓冲区,但默认情况下是与stdin同步的,因此在 C++ 中可以混用 C++ 和 C 风格的输入输出(在不手动取消同步的情况下)。

cinstdin一样是行缓冲,即遇到换行符时才会将数据同步到输入缓冲区。

cin的用法非常多,只列举常用的几种。最常用的就是使用>>符号(我认为该符号形象地体现了“流”的特点)。

用法示例
int a, b;
cin >> a >> b;
char str[20];
cin >> str;

cin对空白字符的处理与scanf一致。即:跳过开头空白字符,遇到空白字符停止读取,且空白字符(包括换行符)残留在缓冲区。

如果不想跳过空白字符,可以使用流控制关键词noskipwsno skip white space),但这只对单个字符有效(类似于scanf中的%c)。

char c;
cin >> noskipws >> c;

注意,cin对象属于命名空间std,如果想使用cin对象,必须在 C++ 文件开头写using namespace std,或者在每次用到的时候写成std::cin

cin.get()

读取单个或指定长度的字符,包括空白字符。

用法示例
char a, b;
char str[20];

// 读取一个字符,读取失败时返回0,多余字符残留在缓冲区(包括换行符)
a = cin.get();

// 读取一个字符,读取失败时返回EOF,多余字符残留在缓冲区(包括换行符)
cin.get(b);

// 在遇到指定终止字符(参数3)前,至多读取n-1个(参数2)字符
// 当不指定终止字符时,默认为换行符\n
// 如果输入的字符个数小于等于n-1(不含终止字符),则终止字符不残留在缓冲区
// 如果输入的字符个数多于n-1(不含终止字符),则余下字符将残留在缓冲区
cin.get(str, sizeof(str), '\n');

cin.get()读取单个字符时,类似于 C 中的getchar(),对空白字符的处理也与其一致。cin.get()读取的字符也可以赋值给整型变量。

cin.get()读取指定长度个字符时,类似于 C 中的fgets(),但在换行符的处理上不同。它们都不会使换行符残留在缓冲区,但fgets()会将缓冲区末尾的换行符\n也写入字符串,而cin.get()会丢弃缓冲区末尾的\n。即:当输入test时,用fgets()读取得到的字符串长度为5,用cin.get()读取得到的字符串长度为4。

cin.getline()

读取指定长度的字符,包括空白字符。

用法示例
char str[20];
cin.getline(str, sizeof(str));    // 第3个参数也可以指定终止字符

cin.getline()cin.get()指定读取长度时的用法几乎一样。区别在于,如果输入的字符个数大于指定的最大长度n-1(不含终止符),cin.get()会使余下字符残留在缓冲区,等待下次读取;而cin.getline()会给输入流设为 Fail 状态,在主动恢复之前,无法再进行正常输入。

getline()

getline()并不是标准输入流istream的函数,而是字符串流sstream的函数,只能用于读取数据给string类对象,使用时也需要包含头文件

如果使用getline()读取标准输入流的数据,需要显式指定输入流。

用法示例
string str;
getline(cin, str);

getline()会读取所有空白字符,且缓冲区末尾的换行符会被丢弃,不残留也不写到字符串结尾。同时,由于string对象的空间是动态分配的,所以会一次性将缓冲区读完,不存在读不完残留在缓冲区的问题。

需要注意的是,假如缓冲区开头就是换行符(比如可能是上一次cin残留的),则getline()会直接读取到空字符串并结束,不会给键盘输入的机会。所以这种情况下要注意先清除开头的换行符。

在 C 中,建议使用scanf()进行格式化读取,用fgets()读取整行,用getchar()读取单个字符。

在 C++ 中,建议使用cin >>进行格式化读取,而cin.get()cin.getlinegetline(string)有各自的适用情况。

注意fgets()cin.get()在对换行符的清理方面有所区别。

二、标准输入流

1. C标准输出

printf()

按照特定格式将stdout缓冲区的内容打印到终端。

用法示例
printf("Number a = %d", a);      // 十进制整数
printf("Number b = %.2f", b);    // 浮点数,保留两位小数
printf("String s = %s", s);      // 字符串

printf()的写法与scanf()十分相像。区别在于scanf()中一般只有格式控制字符,而没有其他普通字符,而printf()中常常是在一串字符中把要替换的内容写为格式控制字符,从而形成格式化输出的效果。

强制输出格式

例如输出浮点数printf("%m.nlf",num),表示将num保留n位小数,长度为m输出。

printf("%5d",25); //25仅有两位不够五位,25前补4位空格输出
printf("%05d",25); //位数不够前补3个0,输出"00025"
printf("%05.2lf",3.1415926);  //输出03.14(小数点也算一位长度)

puts()

将字符串和一个尾随的换行符\n写入到stdout的缓冲区。根据行缓冲的性质,终端也会立即进行打印显示。

用法示例
puts("hello");    // 立即输出hello并换行

puts()对换行符的处理与gets()“相反”。gets()会自动丢弃一个换行符,而puts()则是自动写入一个换行符。

fputs()

将字符串写入指定输出流,可以是文件流、stdoutstderr等。stderr是标准错误流,它是无缓冲的,会立即输出到屏幕,而不是等待换行符才输出。

用法示例
fputs("hello world", stdout);    // 不会立即输出
fputs("hello world\n", stdout);  // 立即输出
fputs("hello world", stderr);  // 立即输出

fgets()一样,fputs()不会主动操作换行符。如果希望立即输出,需要自己加上换行符\n

putchar()

一个字符写入到标准输出流stdout

用法示例
char c = 'x';
putchar(c);

同上,putchar()不操作换行符。如果希望立即输出,需要自己加上换行符\n

2. C++标准输入

cout

coutostream类的一个实例。cout是行缓冲的。

用法示例
char str[] = "hello world";
cout << "str: " << str << endl;

插入endl对象时,将立即清空输出缓冲区并显示,然后输出一个换行符\n

也有cout.put()等函数,不常用。

三、输入输出的加速优化

1.cincout优化

在默认情况下,std::cin/std::cout 是极为迟缓的输入/输出方式,而 scanf/printfstd::cin/std::cout 快得多。

这是因为在默认情况下,cinstdin总是保持同步的,也就是说这两种方法可以混用,而不必担心文件指针混乱,同时coutstdout也一样,两者混用不会输出顺序错乱。由于这个特性,所以导致cincout有许多额外的开销。

那么我们如何禁用这个特性呢?

关闭同步/解除绑定

std::ios::sync_with_stdio(false)
这个函数是一个“是否兼容 stdio”的开关,C++ 为了兼容 C,保证程序在使用了 printf std::cout 的时候不发生混乱,将输出流绑到了一起。
这其实是 C++ 为了兼容而采取的保守措施。我们可以在进行 IO 操作之前将 stdio 解除绑定,但是在这样做之后要注意不能同时使用 std::cin/std::cout scanf/printf。更严格的来说:关闭之后C++ IO和C IO 两者不能混用,否则会造成IO混乱。(这里建议只使用cincout就好)。

tie函数加速

还有一种影响速度的原因是:在默认的情况下std::cin 绑定的是 std::cout ,每次执行 << 操作符的时候都要调用 flush(),这样会增加 IO 负担。

tie 是将两个 stream 绑定的函数,空参数的话返回当前的输出流指针。 可以通过std::cin.tie(0)std:cout.tie(0)(0 表示 NULL)来解除 std::cinstd::cout的绑定,进一步加快执行效率。

在这两种情况优化下,std::cinstd::cout的速度就和scanfprintf基本一样了,甚至更快。

代码:

std::ios::sync_with_stdio(false);
std::cin.tie(0); 
std::cout.tie(0);
//如果编译开启了 C++11 或更高版本,建议使用 std::cin.tie(nullptr);

2.快读快写(读写整数)

read(快读)

众所周知, getchar() 是用来读入 1 byte 的数据并将其转换为 char 类型的函数,且速度很快,故可以用“读入字符——转换为整型”来代替缓慢的读入

每个整数由两部分组成——符号和数字,整数的 ‘+’ 通常是省略的,且不会对后面数字所代表的值产生影响,而 ‘-’ 不可省略,因此要进行判定。
10 进制整数中是不含空格或除 0~9 和正负号外的其他字符的,因此在读入不应存在于整数中的字符(通常为空格)时,就可以判定已经读入结束

C 和 C++ 语言分别在 ctype.hcctype 头文件中,提供了函数 isdigit , 这个函数会检查传入的参数是否为十进制数字字符,是则返回 true ,否则返回 false 。对应的,在下面的代码中,可以使用 isdigit(ch) 代替 ch >= ‘0’ && ch <= ‘9’ ,而可以使用 !isdigit(ch) 代替 ch <‘0’ || ch> ‘9’

inline int read()
{
    int x=0,f=1;
    char ch=getchar();
    while(ch<'0'||ch>'9')
    {
        if(ch=='-')
            f=-1;
        ch=getchar();
    }
    while(ch>='0' && ch<='9')
        x=x*10+ch-'0',ch=getchar();
    return x*f;
}
write(快写)

同样是众所周知, putchar 是用来输出单个字符的函数,因此将数字的每一位转化为字符输出以加速,要注意的是,负号要单独判断输出,并且每次 %(mod)取出的是数字末位,因此要倒序输出。

void write(int x)
{
    if(x<0)
        putchar('-'),x=-x;
    if(x>9)
        write(x/10);
    putchar(x%10+'0');
    return;
}

3.O2优化

代码:

#pragma GCC optimize(2) //实在不行o3优化

你可能感兴趣的:(#,问题杂录,c++,算法)