c算法题中各种输入和输出方法技巧详解!

文章目录

  • 引言
  • 导入io库
  • 输入
    • 各种输入方法
      • `scanf`
        • 格式说明符
        • 基本示例
          • 读入整数
          • 读入其他类型的数字
          • 读入单个字符
          • 读入字符串
          • 扫描字符集合
      • `getchar()`
      • `gets()`
      • `fgets()`
    • 输入技巧
      • 限制每次读入的字符串长度
      • 读入字符但是忽略
      • 判断行尾
  • 输出
    • 输出方法
      • `printf()`
      • `puts()`
      • `putchar()`
    • 输出技巧
      • 输出精度限制
      • 输出到字符串
  • 参考资料

引言

今天刷题,写了好久写完了,一提交0分,改了好久没改对1分,看了一下别人的代码,又简洁又直观,代码量又少,还是满分,心理不平衡了,仔细看了一下,对输入输出的处理处理的好好。吃了这方面的亏,干脆借着这个机会仔细整理一下常见的读取输入的方法,不能再吃这个亏了。。。

导入io库

导入根据不同的环境,推荐使用不同的导入方式

#include//cpp环境下推荐
#include//纯c环境

输入

各种输入方法

scanf

scanf()是最常用的接受输入的方法,使用方式

scanf(控制串,&var1,&var2,...);

其中控制串由三部分组成:

  • 格式说明符:前缀为%,用于告诉方法下次要读入何种数据类型的数据,并顺次放到方法后的变量中.
  • 空白符::由空格(" ")、制表符("\t")和新行符("\n")表示,让方法在输入流中忽略一个或多个空白符(只要存在一个就可以忽略多个)。控制串中的空白符使 scanf() 在输入流中读,但不保存结果,直到发现非空白字符为止。
  • 非空白符:除去格式说明符和空白符以外的其他字符,如逗号,分号,于空白符相同,scanf()在输入流中读,但不保存结果。

scanf("%d/%d,&a,&b),输入"12/3",方法会忽略"/",但如果输入"12 3"或者"12,3",方法不会忽略掉空格或者逗号

格式说明符

以下是scanf方法中的格式说明符,需要注意的是,为了规范,格式说明符和方法后的参数数量最好一致,不一致有可能会出错,有可能不会出错

我本以为一定会出错,但是意外的尝试出了一个没有出错的情况因此没有办法绝对的认为一定会出错

格式说明符 意义
%a 读浮点值(仅适用于 C99)
%A 读浮点值(仅适用于 C99)
%c 读单字符
%d 读十进制整数
%l 读十进制Long型整数
%ll 读十进制Long Long型整数
%i 读十进制、八进制、十六进制整数
%e 读浮点数
%E 读浮点数
%f 读浮点数
%F 读浮点数(仅适用于 C99)
%g 读浮点数
%G 读浮点数
%o 读八进制数
%s 读字符串
%x 读十六进制数(其中abcdef小写,大写时会被忽略)
%X 读十六进制数(其中ABCDEF大写,小写时会被忽略)
%p 读指针值
%n 至此已读入值的等价字符数
%u 读无符号十进制整数
%[ ] 扫描字符集合
%% 读 % 符号(百分号)

我没有刷过太多的算法题,但是在我刷过的有限的算法题中,比较常用的大概就是%d,%[],%c,%s,大部分算法题的输入都是整数和字符串组成的。

但也有一些算法题的输入比较奇特,这时可能会用到%o,%x/%X,

基本示例

读入整数

读入一个整数:

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

读入形式如"1 2 12 42…"这样的多个整数:

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

读入以逗号分隔的多个整数:

int a,b,c;
scanf("%d,%d,%d",&a,&b,&c);
读入其他类型的数字

参考读入整数,其他类型的数字使用方式相同,要注意的是,如果要读取长整数,可以将 l 放在格式说明符的前面(如%ld,%lu);为了读取短整数,可以将 h 放在格式说明符的前面(如%hd),如果要强调。这些修饰符可以与 d、i、o、u 和 x 格式代码一起使用。

读入单个字符

读入单个字符是唯二不忽略空白符的格式符,使用该字符,可以读到空格,制表符,和换行符,注意的是如果该格式符和其他格式符混用,要注意什么时候会读取到换行符,空格等可能不需要的字符

char k;
scanf("%c",&k);

如果格式符之间添加了空格,那么按照规则,会忽略掉全部的空白符直到遇到下一个不是空白符的字符

int i;
char k;
scanf("%d %c",&i,&k);
/**
* 这个时候输入"1\na"和"1a"的效果是一样的,因为无论怎么换行,都属于空白符,会被忽略
*/
scanf("%d%c",&i,&c);
/**
* 这个时候输入"1\na",运行后k会接收到换行符,而不是"a",因为空白符没有被忽略,而%c对所有字符一视同仁。
*/
读入字符串

始终要注意的是读入字符串是scanf()方法的功能,而该方法是属于c的,因此它不支持c++新增的string类型,如果要转换需要进行转换

char str[80];
scanf("%s",&str);//注意这里不需要&,因为str是数组,传入的已经是指针了

string s = str;//需要转换

要注意%s虽然是读入字符串,但它也会忽略空白符,下面例子中的两行scanf()方法是等价的,因为%s本身就有忽略空白符的功能。

char stra[80];
char strb[80];asdasd
scanf("%s %s",stra,strb);
scanf("%s%s",stra,strb);

同时,按照该方法,虽然会忽略空白符,但是会主动的在最后一个字符后添加"\0"表示字符串结束(也因此,char数组不需要初始化就可以接受字符串,一般情况下不影响操作),因此在设置char数组长度时候,最好要比理论中的最大长度多预留一个长度。

char str[5];//如果题目接受的最大字符串长度是4,那么设置的数组长度最好大于等于5
scanf("%s",str);

要注意的是,方法对空白符的忽略不是抛弃了空白符,如果没有继续读下去的话,接受字符串后的空白符是会保留在缓冲区的,这个时候使用%c接收是可以接收到,这个可能困扰了很多人,一定要注意。

char k;
char str[10];
scanf("%s%c",str,&k);
/**
* 如果输入"abcd",那么会直接运行结束,str="abcd",k='\n'
*/
扫描字符集合

%[]大概是所有格式符里最特殊的一个,用途是扫描满足集合条件是所有字符直到碰到第一个不满足的,以字符串形式返回(和%s相同),这种自定义条件的特性赋予了它忽略空白符的功能,所以%c%[]是唯二有能力接收空白符的格式符,不同的是%c是强制的,而%[]是可选的

扫描字符集合其实有点像简化的正则表达式,实际上是对单个字符的条件界定,比如,匹配全部字母是%[a-zA-Z],匹配全部数字是%[0-9],另外扫描字符集还支持取反操作,即在括号内的开始添加^,如要匹配非数字,可以表示为%[^0-9];要读取一整行,并且忽略其中的空白符,可以这样写:

char k[80];
scanf("%[^\n]",k);

这个例子是经常使用到的一种接收形式。

getchar()

字面意思,接收单个字符,使用方法:

char a;
a = getchar();

实际上效果等同于

char a;
scanf("%c",&a);

gets()

字面意思,读取多个字符,实际上是读取一整行,使用方法

char str[80];
gets(str);

实际效果等同于:

char str[80];
scanf("%[^n]",str);

由于gets()不检查字符串string的大小,必须遇到换行符或文件结尾才会结束输入,因此容易造成缓存溢出的安全性问题,导致程序崩溃,可以使用fgets()代替。

另外,有的时候代码中可能会出现getline()方法,虽然格式可能相同,但实际上这是c++的输入方法。

fgets()

是对gets()方法的扩展,gets()是从标准输入流中读取,而fgets()是从文件输入流中读取,但是文件输入流并不局限于普通的文件,只要是流都可以用来输入,使用方法:

char str[80];
fgets(str,79,stdin);

方法与gets()相比,多添加了两个参数,第二个参数限定要读取的最大长度,最终读取的长度不超过还未读取的剩余行长度;第三个参数说明从哪个流读取输入,通过定义stdin,我们就可以定义从标准输入中读取。

注意:fgets()方法接受到行尾时会接收换行符!,这一点非常特殊,一定要注意。

输入技巧

上面展示的方法基本上足够应用大部分场景,但是有的时候,用一些特殊的方法,能让我们更有效率的接受字符串,我总结了以下几个

限制每次读入的字符串长度

在百分号(%)与格式码之间添加一个整数可以限制读入的最大字符数,超出字符串的部份将留在缓冲区等待下次读取。

例如:向变量A读入不多于 20 个字符时的代码:

char A[20];
scanf("%20s",&A);

注意读入字符串需要注意数组长度的设置,上面的例子实际上是不严谨的,因为读取到结束时候虽然会忽略空白符,但是会添加"\0"用来标识结束,如果刚好填满数组的话,会导致内存溢出,从而可能出现一些未知错误,正确的写法应该如下(假设需要读入最长20个字符的字符串):

char A[21];
scanf("%20s",&A);

最后,这种方式不仅仅局限于输入字符串,限制的长度只是限制了该方法一次性能从缓冲区看到的字符串长度,也就是说,还可以用于接收整数,浮点数等

int a;
scanf("%2d",&a);
/**
* 输入"12345",运行后 a=12
*/

读入字符但是忽略

scanf( "%d%*c%d", &x, &y );
/**
* 输入 "10/20"
* 10放入变量x,20放入变量y,'/'被接受但是被忽略
* 这种方式可以用来匹配中间分隔符未知的情况
*/

判断行尾

一般算法题中,根据输入一般是能确定输入中每一行的长度(或者要读取多少次),但是仍然有一些题没有明确的给出,需要手动判断或后期处理,简单举一个例子:

给N行数字,每一行由纯数字组成,保证每一行的数字个数为偶数个,按相邻的两个数字为一个数(不重叠),对每一行求和并输出
如:对于123456,被分为12+34+56=102

对于这个问题,单纯的读取连续的两个数字,按照上面的技巧,是很容易的,格式符为%2d,这个问题的主要难点是我们不好判断一行什么时候结束,如果单纯的使用scanf()方法,没有很好的解决方案,只能通过一个字符一个字符的读取然后再组装成数字。这样实际上白白浪费了时间和精力,有没有更好的方法?当然有,那就是使用方法sscanf()

sscanf()方法和scanf()方法基本一样,唯一不同的是其前面多了一个参数,传递进去的是char型数组,通过该方法,我们可以先用gets()方法读取一整行,用strlen()方法求出行长度,随后我们就可以用sscanf()方法来二次提取,核心代码如下:

char str[80];
gets(str);
int len = strlen(str);//需要引入cstring库或string.c
int sum = 0;
for(int i = 0;i<len/2;i++)
{
	int num;
	sscanf(str+i*2,"%2d",&num);
	sum += num;
}

其中第一个参数传入的是char型数组(实际上传入的是指针,str表示的是第1个元素所对应的位置,每加1就向下迭代一次,c里面字符串没有办法切片,但可以用这种方法更改字符串的起始位置)

输出

输出方法

printf()

printf()方法和scanf()基本对应,不过在输入变量的时候,不需要指定地址(也就是不需要添加&

如:

int a = 3;
printf("The number is %d.",a);

scanf()输出字符串的时候只支持char数组或指针,不支持string类型,如果要输出同样需要转换

char str[80];
printf("%s",str);
string s;
printf("%s",s.c_str());
printf("%s",s.data());//两者等价,完全相同,底层调用的是一个方法

puts()

puts()方法用于输出字符串,虽然指定了格式必须是char型指针或数组,但仍然非常的便捷,尤其是该方法会自动在行尾添加换行符,不用我们手动添加换行符了

puts("hello world!");//这样是可以的
string k;
//puts(k);//这样是不可以的
puts(k.c_str());
puts(k.data());

char str[80];
puts(str);
printf("%s\n",str);//两个方法等价

putchar()

可以看到io方法大多都是对应着写的…该方法也一样,输出一个char类型的字符

char a = 'a';
putchar(a);
putchar('b');

输出技巧

输出精度限制

按特定精度输出的很多题的要求,主要是在输出的数字前面或者后面加0,实现方法如下:

double d = 123.45678;
printf("%*.*f",20,10,d);

//输出:
      123.4567800000

两个星号分别用来指定宽度和精度,由后面的两个数指定,其中:

  • 精度指小数点后(不算小数点)部份的长度,不够补零,多余则是四舍五入(好像有的标准不一样12,使用时可以具体测试一下)
  • 宽度指整个数字部份(包括小数点)的长度,如果不够则右对齐,在左侧补空格

如果要在前面补0(只能补零),则使用如下的代码:

double d = 123.45678;
printf("%0*.*f",20,10,d);
//输出:
000000123.4567800000

如果要省略参数,也可以这样写,不过没有办法指定左侧补0

double d = 123.45678;
printf("%20.10f",d);

//输出:
      123.4567800000

实际上,该方法的格式化字符串形式如下:

%[flags][width][.precision][length][type]
flags(标识) 描述
- 在给定的字段宽度内左对齐,默认是右对齐(参见 width 子说明符)。
+ 强制在结果之前显示加号或减号(+ 或 -),即正数前面会显示 + 号。默认情况下,只有负数前面会显示一个 - 号。
(space) 如果没有写入任何符号,则在该值前面插入一个空格。
# 与 o、x 或 X 说明符一起使用时,非零值前面会分别显示 0、0x 或 0X。与 e、E 和 f 一起使用时,会强制输出包含一个小数点,即使后边没有数字时也会显示小数点。默认情况下,如果后边没有数字时候,不会显示显示小数点。与 g 或 G 一起使用时,结果与使用 e 或 E 时相同,但是尾部的零不会被移除。
0 在指定填充 padding 的数字左边放置零(0),而不是空格(参见 width 子说明符)。
width(宽度) 描述
(number) 要输出的字符的最小数目。如果输出的值短于该数,结果会用空格填充。如果输出的值长于该数,结果不会被截断。
* 宽度在 format 字符串中未指定,但是会作为附加整数值参数放置于要被格式化的参数之前。
.precision(精度) 描述
.number 对于整数说明符(d、i、o、u、x、X):precision 指定了要写入的数字的最小位数。如果写入的值短于该数,结果会用前导零来填充。如果写入的值长于该数,结果不会被截断。精度为 0 意味着不写入任何字符。对于 e、E 和 f 说明符:要在小数点后输出的小数位数。对于 g 和 G 说明符:要输出的最大有效位数。对于 s: 要输出的最大字符数。默认情况下,所有字符都会被输出,直到遇到末尾的空字符。对于 c 类型:没有任何影响。当未指定任何精度时,默认为 1。如果指定时不带有一个显式值,则假定为 0。
.* 精度在 format 字符串中未指定,但是会作为附加整数值参数放置于要被格式化的参数之前。
length(长度) 描述
h 参数被解释为短整型或无符号短整型(仅适用于整数说明符:i、d、o、u、x 和 X)。
l 参数被解释为长整型或无符号长整型,适用于整数说明符(i、d、o、u、x 和 X)及说明符 c(表示一个宽字符)和 s(表示宽字符字符串)。
L 参数被解释为长双精度型(仅适用于浮点数说明符:e、E、f、g 和 G)。

type见格式说明符

输出到字符串

sscanf()对应的是sprintf(),作用也很明显,就是将各种变量类型格式化成字符串,而sprintf()方法和printf()也几乎完全相同,只是在前面加了一个参数用来放置存放输出的字符串变量

int a,b,d;
char str[80];
sprintf(str,"%d+%d=%d",a,b,c);

参考资料

scanf详解
整理:C++中sprintf()函数的使用详解
printf宽度控制和精度控制

C语言fgets()函数:从文件流中读取一行或指定个数的字符


  1. 数值修约 ↩︎

  2. 奇进偶舍 ↩︎

你可能感兴趣的:(算法)