更详细了解printf和scanf

目录

前言

一、scanf

           1.返回值

           2.格式说明符

           3.输入流中的一些问题

 二、printf

   1.返回值

   2.转换说明符

a.        flags(标志)       

b.        width(最小宽度)

c.        .precision(精度)

d.        length(类型长度) 

   2.转义字符

总结


前言

        最基本的两个被声明在头文件stdio.h里库函数,我们经常使用,但经过我的一番了解,我发现它们并没有我想象的那般简单,暗藏了很多小玄机,下来让我详细谈一谈。


一、scanf

         函数 scanf() 从标准输入流(一般是键盘)stdin中读入内容,并存放在相应地址中。

   1.返回值

        scanf函数的返回值为int类型,返回输入的数据个数,下面这条代码就可以印证,当输入之后,都能对应打出“ok”和“okla”,当然如果不嫌麻烦,可以存更多的变量试下。

int main()
{
    int a = 0;
    int b = 0;
    if(scanf("%d", &a) == 1)
        printf("ok");
    if(scanf("%d%d", &a, &b) == 2)
        printf("okla");
    return 0;
} 

当然也可以直接一点,用以下方式印证,运行后结果为1和2。

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

        如果遇到错误或遇到end of file(一般是多组输入后停止输入的回车以这种形式返回),返回值为EOF。在while循环中以EOF作为文件结束标志举例如下,想结束循环需要按Ctrl+z 或者Ctrl+d加Enter来告诉系统到了EOF。

while(scanf("%d", &a) != EOF)

        在C语言中,或更精确地说成C标准函数库中表示文件结束符(end of file)。在while循环中以EOF作为文件结束标志,这种以EOF作为文件结束标志的文件,必须是文本文件。在文本文件中,数据都是以字符的ASCII代码值的形式存放。我们知道,ASCII代码值的范围是0~127,不可能出现-1,因此可以用EOF作为文件结束标志。
————————————————
参考的原文链接:https://blog.csdn.net/henu1710252658/article/details/83040281

   2.格式说明符

        几个未注意到的格式说明符:

c 读入指定个数的字符,未指定默认为一个,如%2c为两个(d也可以这样)
u 读入无符号符号十进制整数
i 读入可选有符号整数
o 读入可选有符号八进制整数
x,X 读入可选有符号十六进制整数
n 记录有效字符数量,是int*类型指针,不会占用返回值数目
% 扫描字符,读入%符号

        (对n举例说明,见如下代码)

//%n输出的是有效字符数量
scanf("%d%d%n", &a, &b, &c);
//如果输入2356 12,则c等于6,而scanf的返回值仍然为2
scanf("%c%n", &a, &b);
//输入“kfsjfe”后,b等于1,而不是6,因为%c只取一个字符

        还有一个有趣的修饰符,星号修饰符,下面对 * 修饰符举例说明,见如下代码:

//比如,输入三个数,我只想要中间那个
scanf("%*d%d%*d", &a);
//输入1 2 3
printf("%d", a);
//打印2


//再比如
int a = 0;
int b = 0;
scanf("%d%*d", &a, &b);
//输入2 2
printf("%d %d\n",a,b);
//打印2 0
a = 0, b = 0;
scanf("%d%*c%d", &a, &b);
//输入 2 a 2
printf("%d %d\n",a,b);
//打印2 2
a = 0, b = 0;
scanf("%d%*s%d",&a,&b);
//输入 2 abc 2
printf("%d %d",a,b);
//打印2 2

          需要注意的几点:        

        (1)首先我们拉回到scanf定义看一眼,它是将读入值存放在相应地址中,因此我们一定不能直接用变量名,记着加取地址符‘&’(因为数组名就是数组本身的地址,所以,数组不用加)。

        (2)scanf函数中没有类似printf的精度控制,因此scanf("%4.4f",&a); 这一类是非法的,不能企图用此语句输入小数为4位的实数。

        (3) scanf() 返回等于成功赋值的域数的值,但由于星号修饰符而读入未赋值的域不计算在内,遇到文件结束则返回EOF;若出错则返回0。

        ANSI C 标准向 scanf() 增加了一种新特性,称为扫描集(scanset)。 扫描集定义一个字符集合,可由 scanf() 读入其中允许的字符并赋给对应字符数组。 扫描集合由一对方括号中的一串字符定义,左方括号前必须缀以百分号。 例如,以下的扫描集使 scanf() 读入字符 A、B 和 C:

%[ABC]

        使用扫描集时,scanf() 连续吃进集合中的字符并放入对应的字符数组,直到发现不在集合中的字符为止(即扫描集仅读匹配的字符)。返回时,数组中放置以 null 结尾、由读入字符组成的字符串。

        用字符 ^ 可以说明补集。把 ^ 字符放为扫描集的第一字符时,构成其它字符组成的命令的补集合,指示 scanf() 只接受未说明的其它字符。

        对于许多实现来说,用连字符可以说明一个范围(ISO C99标准没有规定)。例如,以下扫描集使 scanf() 接受字母 A 到 Z:

%[A-Z]

        重要的是要注意扫描集是区分大小写的。因此,希望扫描大、小写字符时,应该分别说明大、小写字母。

   

长度修饰符

        hh与d, i, o, u, x, X, or n配合使用,表示对应一个signed char或unsigned char数据。

        h与d, i, o, u, x, X, or n配合使用,表示对应一个short int或unsigned short int数据。

        l 与d, i, o, u, x, X, or n配合使用,表示对应一个long int或unsigned long int数据;与a, A, e, E, f, F, g配合使用表示对应一个double数据;与c,s,[配合使用表示对应wchar_t数据。

        ll与d, i, o, u, x, X, or n配合使用,表示对应一个long long int或unsigned long long int数据。

        j与d, i, o, u, x, X, or n配合使用,表示对应一个intmax_t或uintmax_t数据。

z与d, i, o, u, x, X, or n配合使用,表示对应一个size_t数据(或与size_t对应的有符号整型数据)。

        t与d, i, o, u, x, X, or n配合使用,表示对应一个ptrdiff_t数据(或与ptrdiff_t对应的无符号整型数据)。

        L 与a, A, e, E, f, F,g or G配合使用,表示对应一个long double数据。

        如果长度修饰符与格式说明符不匹配则引起未定义的行为。

————————————————

参考来源于百度

   3.输入流中的一些问题

        我刚学C语言时,曾遇上一个问题,我写了如下代码(只展示scanf这一行),可当我输入数值10 20,代码却跑不起来,后来研究一阵才知道如果格式控制串中有非格式字符串则输入时也要输入该非格式字符

scanf("%d,%d", &a, &b);
//输入10 20

        又倒腾一阵,我发现,如下代码中输入1 23fafa,打印出来只是123,搜索后明白在输入多个数值数据时,若格式控制串中没有非格式字符作输入数据之间的间隔,则可用空格,TAB或回车作间隔。C编译在碰到空格,TAB,回车或非法数据时即认为该数据结束

scanf("%d%d", &a, &b);
//输入1 23fafa
printf("%d%d", a, b);
//打印123

        但是后来问题又来了:

        在我输入有空格的字符串时,输出并不如我所愿

int main(void)
{
    char ch[];
    scanf("%s", ch);
    printf("%s", ch);
    return 0;
}
//输入This's my world!
//打印出来却只是This's

        然后我便想到上面所说,编译时遇到空格就会认为数据结束,所以我猜想,scanf对输入流扫描时,到空格截止,空格后的数据并没有进入键盘缓冲区,我便写了如下代码做出验证:

int main()
{
    char ch[], ch1[], ch2[];
    scanf("%s", ch);
    printf("%s\n", ch);
    scanf("%s", ch1);
    printf("%s\n", ch1);
    scanf("%s", ch2);
    printf("%s\n", ch2);
    return 0;
}
//仅输入一次,输入This's my world!
//不出所料,输出结果为
/*
This's
my
world
*/

        由此不难看出,输入流中有空格时,scanf扫描数据遇到空格就会中断,这样就导致了输入流的东西无法全部塞入键盘缓冲区,那我该如何一次将这句字符串一次打印出来呢?

        这时候就需要用到前面百度搜到的东西(见一、2中的引用块),做如下处理即可:

int main()
{
char ch[];
scanf("%[^\n]", ch);
//让scanf("%s", ch);不能接收空格符
printf("%s\n", ch);
return 0;
}
//输入This's my world!
//输出This's my world!

        到这里就会又想到一个问题,既然有输入流无法一次全部放入键盘缓冲区,那有没有可能输入流全部塞进缓冲区,但某种情况导致输入流堵在了缓冲区,导致下一次输入时,缓冲区里还留着上一次输入流的东西 ,答案是当然有,举例如下:

//第一种情况
int main()
{
    int a;
    char c;
    scanf("%d", &a);
    scanf("%c", &c);
    printf("a=%d c=%c\n", a, c);
    return 0;
}
//输入1
//输出却是a=1 c=
//输出中c没有东西!!!

  

        我们可以发现scanf("%c", &c);这句不能正常接收字符,为了找出原因我写了如下代码:

 更详细了解printf和scanf_第1张图片

        看看scanf()函数赋给C到底是什么,结果是c=10 ,ASCII值为10是什么?就是换行符\n。这样就能联想到我每击打一下"Enter"键,向键盘缓冲区发去一个“回车”(\r),一个“换行"(\n),在这里\r被scanf()函数处理掉了,而\n被scanf()函数“错误”地赋给了c。

        解决方法也很简单,只需要在输入a后加上getchar(),这样缓冲区的\n就被getchar扫描使用掉了,c就可以正常输入了。

        根据scanf中非空格符一一匹配的特点,我如下处理应该也可以

更详细了解printf和scanf_第2张图片

         但是这种方法并不推荐,推荐第一种,原因如下:

更详细了解printf和scanf_第3张图片

第二种情况
int main()
{
    int a = 0;
    int b = 0;
    int c = 0;
    int ret = 0;
    ret=scanf("%d%d%d", &a, &b, &c);
    printf("第一次读入数量:%d\n", ret);
    ret=scanf("%d%d%d", &a, &b, &c);
    printf("第二次读入数量:%d\n", ret);
    return 0;
}

 

        这种情况特殊,但我猜测可能因为输入了一个不匹配%d的'b',使得输入流阻塞了,为了验证,我把第二次输入的首个格式符改为%c,来看看第二次还能否输入

更详细了解printf和scanf_第4张图片

         这样看来,和我们猜想一致,那么可能有人就想,和第一种情况那样处理不就好了吗?这样想,对了,但没有完全对。因为,如果我输入的数据是1 b c呢,你得写两次,或者依次输入的数据更多,错误的匹配类型也更多,你也得一个个写吗?这时候就可以用while循环,一次搞定了

int main()
{
    int a, b, c, ret;
    ret = scanf("%d%d%d", &a, &b, &c);
    while((c=getchar()) != '\n'&&c != EOF);
    //我只需要让getchar把缓冲区里的东西用掉就好,至于用哪去,去干嘛,那就和我无关了
    //因此,while循环里什么都不用写
    printf("第一次读入数量:%d\n", ret);
    ret=scanf("%d%d%d", &a, &b, &c);
    while((c=getchar()) != '\n' && c != EOF);
    printf("第二次读入数量:%d\n", ret);
    return 0;
}

 二、printf

        函数printf() 按规定格式输出信息, 在输出格式 format(这个接下来讲,吊吊胃口) 的控制下,将其参数进行格式化,并在标准输出设备(显示器、控制台等)上打印出来,输出的字符串除了可以是字母、数字、空格和一些数字符号以外,还可以使用一些转义字符表示特殊的含义。

   1.返回值

        printf 函数的返回值为输入的所有东西数量,不仅仅只是你能看到的那些字符,计数还包括空格换行符等等,但大家还需注意一点,字符串中的空字符(即'\0)并不会被算入返回值

int main()
{
    int a = 0;
    a = printf("%s", "abn nid\n");
    printf("%d", a);
}
//输出为
//abn nid
//8

 

   2.转换说明符

        在开始讲printf函数的地方,我提到了一个叫format的东西,它就是格式控制字符串,包含了两种类型的对象:普通字符和转换说明,printf函数在输出时,普通字符将原样不动地复制到标准输出,转换说明并不直接输出而是用于控制 printf 中参数的转换和打印,就和scanf中的格式说明符很相似。

        转换说明组成(format字符串)如下,其中绿色的是可选的:

格式: % flags width  .precision length specifier
说明: % 标志 最小宽度 .精度 类型长度 说明符

        printf中的转换说明符可以说和scanf中完全一样,一一对应,因此就不过多赘述,我将转换说明符单拎出来主要是想说明绿色的这几个。

a.        flags(标志)       

        用于规定输出的样式

- 在给定宽度内左对齐,其他用空格补齐
+ 强制显示数字正负,即正数前加'+'(默认不加),负数前加'-'
(空格) 同上,不过和+的区别是正数前加空格
# 说明符是 o、x、X 时,增加前缀 0、0x、0X
说明符是 e、E、f、g、G 时,一定使用小数点
说明符是 g、G 时,尾部的 0 保留
0 在数字数据的前面用零填充宽度(类似右对齐),存在-时,不起作用

        举例说明:

int main() 
{
    printf("*%-10d*\n", 24);                            
//左对齐,右边补空格
    printf("*%+d*\n", 24);                            
//输出正负号
    printf("%x %X %#x\n", 24, 24, 24);                     
//输出0x
    printf("**%d**% d**% d**\n", 24, 24, -24);             
//正号用空格替代,负号输出
    printf("**%5d**%5.3d**%05d**%05.3d**\n", 24, 24, 24, 24);  
//前面补0
    return 0;
}

  更详细了解printf和scanf_第5张图片

b.        width(最小宽度)

        用于控制显示字段的宽度

digit(数字) 字段宽度的最小值,小于该数则右对齐,用空格填充;大于该数,不会截断输出
* 宽度在 format 字符串中未规定,使用星号标识附加参数,指示下一个参数是width

        举例说明:

int main() 
{
    printf("*%2d*\n", 12345);         
//输出的字段长度大于最小宽度,不会截断输出
    printf("*%10d*\n", 12345);        
//默认右对齐,如何左对齐,参见上面flag中的-     
    printf("*%*d*\n", 2, 12345);      
//等价于 printf("*%2d*\n", 12345)
    return 0;
}

 

c.        .precision(精度)

         用于指定输出精度

.digit(n) 对于整数说明符:指定了要打印的数字的最小位数,短于该数(默认右对齐),结果会用零来填充,长于该数,结果不会被截断。精度为 0 意味着不写入任何字符
对于 e、E 和 f 说明符:要在小数点后输出的小数位数
对于 g 和 G 说明符:要输出的最大有效位数
对于 s 说明符:要输出的最大字符数(默认遇到空字符停止)
对于 c 说明符:没有任何影响
当未指定时,默认为 1,指定时只使用点而不带有一个显式值,则默认为0
.* 精度在 format 字符串中规定位置未指定,使用.*,则指示下一个参数是精度

        举例说明:

int main() 
{
    printf("*%4.2f*\n", 6666.66);
//正常打印
    printf("*%3.1f*\n", 6666.66);
//宽度不够,不会被截断,但精度不够会被截断
    printf("*%10.3f*\n", 6666.66);
//右对齐,空格填充
    printf("*%.*f*\n", 4, 6666.66);
//指示下一个参数为精度
    printf("*%.f*\n", 6666.66);
//不输入,精度默认为0
    return 0;
}

 更详细了解printf和scanf_第6张图片

d.        length(类型长度) 

        用于控制待输出数据的数据类型长度

h 参数被解释为短整型或无符号短整型(仅适用于整数说明符:i、d、o、u、x 和 X)
l 参数被解释为长整型或无符号长整型,适用于整数说明符(i、d、o、u、x 和 X)及说明符 c(表示一个宽字符)和 s(表示宽字符字符串)

         对于h,我相信大家都可以理解,但是看到l,我相信大家一定都无法理解宽字符有什么存在的意义,对此,我不过多赘述,不过大家可以看看以下我找到

        回想C语言中的char类型,大小是固定的一个字节。很明显如果C语言想要支持Unicode编码(或者其他的多字节编码),必须创造一种两个字节大小的类型。宽字符就这样诞生了,

        “宽”字表示比char类型大,大小为两个字节。宽字符用wchar_t关键字定义。同理,一个字符串也要告诉编译器自己是否是Unicode编码。具体方法是在字符串前加上L表示Unicode编码。
————————————————
参考的原文链接:https://blog.csdn.net/qq_51619737/article/details/124219018

   2.转义字符

        当我们在打印是,会遇到 ' " 之类无法打印,而被识别成引用字符,字符串的标志,还有当我们想有规格的打印一些东西,总不能只靠敲空格,甚至做不到换行,这时候就需要转义字符了

转义字符 意义 输 出 结 果 ASCII码值(十进制)
\0 空字符(NUL) 没有东西 000
\a 警告(alert) 产生声音或视觉信号 007
\b 退格(BS) 将当前位置后退一个字符 008
\t 水平制表(HT) 跳到下一个TAB位置 009
\n 换行(LF) 将当前位置移到下一行开头 010
\v 垂直制表(VT) 将当前位置移到下一个垂直表对齐点 011
\f 换页(FF) 将当前位置移到下页开头 012
\r 回车(CR) 将当前位置移到本行开头 013
\" 代表一个双引号字符 输出双撇号字符" 034
\' 代表一个单引号字符 输出单撇号字符' 039
\? 代表一个问号 输出问号字符? 063
\\ 代表一个反斜线字符''\' 输出反斜杠字符\ 092
\ddd 1到3位八进制数所代表的任意字符 与该八进制码对应的字符 三位八进制
\xhh 十六进制所代表的任意字符 与该十六进制码对应的字符 十六进制

        举例说明:

int main()
{
    printf("%d\n", strlen("c:\test\129"));
//来深入理解下
    return 0;
}
//输出结果应该为8
//c,:,\t,e,s,t,\12,9  共计8个

 

总结

        这就是这篇博客想和大家分享的关于这两个最基础的函数的小细节,因为我是新手,如有何处疏漏,错误,望各位大佬不吝指点,万分感谢!

你可能感兴趣的:(c语言)