C语言入门篇——输入输出篇

目录

1、printf()函数

1.1、printf()函数中的标记

1.2、输出最小宽度(width)

1.3、精度(.precision)

2、scanf()函数

2.1、scanf(“输入控制符”, 输入参数)

2.2、scanf(“输入控制符非输入控制符”, 输入参数);

2.3、字符串和转义字符

2.4、注意事项


1、printf()函数

printf()函数是C语言标准输出函数,用于将格式化后的字符串输出到标准输出(对应终端的屏幕)。使用printf函数需要声明在头文件#include<stdio.h>下。

认识一个函数先了解它的原型

int printf ( const char * format, ... );

返回值:正确返回输出的字符总数,错误返回负值。

那怎么调用printf()函数呢?调用格式:

printf("格式化字符串", 输出表列)

格式化字符串包含三种对象,分别为:
(1)字符串常量;
(2)格式控制字符串;
(3)转义字符。

字符串常量原样输出,在显示中起提示作用。输出表列中给出了各个输出项,要求格式控制字符串和各输出项在数量和类型上应该一一对应。其中格式控制字符串是以 % 开头的字符串,在 % 后面跟有各种格式控制符,以说明输出数据的类型、宽度、精度等。后面将详细介绍字符串。

printf() 的格式控制字符串组成如下:

%[flags][width][.prec][length]type
%[标志][最小宽度][.精度][类型长度]类型

上面五个选项中,类型是必不可少的,type用于规定输出数据的的类型。下表列出一些转换说明和各自对应的输出类型。

转换说明及其打印结果
转换说明输出示例
%a浮点数、十六进制数和p记数法 (C99/C11)printf("%a",3.14);输出0x1.91eb851eb851fp+1

%A

浮点数、十六进制数和p记数法 (C99/C11)printf("%A",3.14);输出0X1.91EB851EB851FP+1
%c单个字符printf("%c",a);输出字符a
%d有符号十进制整数printf("%d",10);输出十进制数字10
%e浮点数,e记数法printf("%e",3.14e10);输出3.140000e+10
%E浮点数,e 记数法printf("%E",3.14e10);输出3.140000E+10
%f浮点数,十进制记数法printf("%f",3.14);输出浮点数3.14
%g根据值的不同,自动选择%f 或%e。%e 格式用于指数小于-4 或者大于或等于精度时

printf("%g",0.00000123);输出1.23e-07

printf("%g",0.123);输出0.123

%G根据值的不同,自动选择%f 或%E。%E 格式用于指数小于-4 或者大于或等于精度时

printf("%G",0.00000123);输出1.23E-07

printf("%G",0.123);输出0.123

%i有符号十进制整数(与%d 相同)printf("%i",123);输出123
%o无符号八进制整数printf("%o",12);输出14
%p指针printf("%p","hello");输出00B07B30
%s字符串printf("%s","hello");输出hello
%u无符号十进制整数printf("%u",123);输出123
%x无符号十六进制整数,使用十六进制数 0fprintf("%x",123);输出0x7b
%X无符号十六进制整数,使用十六进制数 0Fprintf("%X",123);输出0x7B
%%打印一个百分号printf("%%");输出%

其中有一些我们在之前就有接触过(%d,%c,%u等等),这里并不是全部,但以上这些是比较常用的,希望读者可以多加练习从而记住。

1.1、printf()函数中的标记

printf()函数的标记
标记含义
-待打印项左对齐。即,从字段的左侧开始打印该项项
+有符号值若为正,则在值前面显示加号:若为负,则在值前面显示减号
空格有符号值若为正,则在值前面显示前导空格(不显示任何符号): 若为负,则在值前面显示减号+标记覆盖一个空格
#把结果转换为另一种形式。如果是%o格式,则以0开始: 如果是%x或X格式,则以0x或 0X开始:对于所有的浮点格式,#保证了即使后面没有任何数字,也打印一个小数点字符。对于%q 和%G 格式,#防止结果后面的 0被删除
0对于数值格式,用前导0代替空格填充字段宽度。对于整数格式,如果出现-标记或指定精度,则忽略该标记

这里附上我当时学习的代码和运行截图:

1.2、输出最小宽度(width)

用十进制整数来表示输出的最少位数。若实际位数多于指定的宽度,则按实际位数输出,若实际位数少于定义的宽度则补以空格或0。width的可能取值如下:

width描述示例
数值十进制整数printf("%06d",1000);输出:001000
*星号。不显示指明输出最小宽度,而是以星号代替,在printf的输出参数列表中给出printf("%0*d",6,1000);输出:001000

1.3、精度(.precision)

精度格式符以“.”开头,后跟十进制整数。可取值如下:

.precision描述
.数值进制整数。
(1)对于整型(d,i,o,u,x,X),precision表示输出的最小的数字个数,不足补前导零,超过不截断。
(2)对于浮点型(a, A, e, E, f ),precision表示小数点后数值位数,默认为六位,不足补后置0,超过则截断。
(3)对于类型说明符g或G,表示可输出的最大有效数字。
(4)对于字符串(s),precision表示最大可输出字符数,不足正常输出,超过则截断。
precision不显示指定,则默认为0
.*以星号代替数值,类似于width中的*,在输出参数列表中指定精度

在 printf() 的实现中,在调用 write 之前先写入 IO 缓冲区,这是一个用户空间的缓冲。系统调用是软中断,频繁调用,需要频繁陷入内核态,这样的效率不是很高,而 printf 实际是向用户空间的 IO 缓冲写,在满足条件的情况下才会调用 write 系统调用,减少 IO 次数,提高效率。

可以参考一下我之前写的则篇博客:printf函数输出问题_sakura0908的博客-CSDN博客

之前说过,大部分C函数都有一个返回值,这是函数计算并返回给主调程序的值。可以把返回值赋给一个变量,也可以用于计算,还可以作为参数传递。总之,可以想其他值一样使用。

printf()函数也有一个返回值,它返回打印字符的个数,不知道这个函数值的意义的话,当我们做C语言相关题目的时候总会写错。这里给读者一个很经典的测试案例,看一下读者能否得出正确结果

int main()
{
    int A=43;
    printf("%d\n",printf("%d",printf("%d",A)));
}

同一段代码,可能有些人得出的结果是不一样,但通过运行代码,通过系统获得的答案是4321,有可能和读者心目中的答案不一样,现在我们来剖析一下这段代码的结果,为什么是4321呢?

前面讲述了printf()函数的返回值是打印字符的个数,而且函数返回值也可以作为函数参数。结合下图应该就很容易理解了。

2、scanf()函数

学完printf()函数,接下来学习scanf()函数,scanf()函数是C语言标准输入函数,通过键盘给程序中的变量赋值。使用scanf函数需要声明在头文件#include<stdio.h>下。scanf函数返回成功读取的项数。

scanf函数的原型:

int scanf(const char *format, ...);

scanf函数的使用格式:

1、scanf(“输入控制符”, 输入参数);

2、scanf(“输入控制符非输入控制符”, 输入参数);

2.1、scanf(“输入控制符”, 输入参数)

功能:将从键盘输入的字符转化为“输入控制符”所规定格式的数据,然后存入以输入参数的值为地址的变量中

int main(void)
{
    int input;
    input = 10;
    printf("input = %d\n", input);

    //希望在程序里面可以修改input的值,就需要用到scanf函数
    scanf("%d", &input);
    printf("input = %d\n", input);

    return 0;
}

“输入控制符”和“输出控制符”是一模一样的。 比如一个整型数据,通过 printf 输出时用%d输出,通过 scanf 输入时同样是用%d。怎么理解scanf函数这一行的代码呢?

首先要理解的是从键盘输出的全部都是字符,比如从键盘输入123,它表示的并不是数字123,而是字符'1'、字符'2'和字符'3'。操作系统内核就是这样操作的,操作系统在接受键盘数据时都将它当成字符来接收的。这时就需要“输入控制符”将它转化一下。%d的含义就是要将从键盘输入的这些合法的字符转化成一个十进制数字。经过 %d 转化完之后,字符 123 就是数字 123 了。然后就要理解&这个符号是什么,&是一个取地址运算符,&后面加变量名表示“该变量的地址”,所以&input就表示变量input的地址。&input又称为“取地址input”,就相当于将数据存入以变量input的地址为地址的变量中。

ok,现在返回来看一下上面代码的scanf函数的用法,这句语句的意思就是:从键盘上输入字符 30,然后%d将这两个字符转化成十进制数 30,最后通过 “取地址 input” 找到变量 input 的地址,再将数字 30放到以变量 input 的地址为地址的变量中,即变量 input 中,所以最终的输出结果就是input=30

注意:
为什么不直接说“放到变量input中”?而是说“放到以变量input的地址为地址的变量中”?因为这么说虽然很绕口,但是能加强对 &input 的理解,这么说更能表达 &input的本质和内涵。很多人在学习 scanf 的时候,经常将“变量 i”和“变量 i 的地址”混淆,从而思维开始混乱,等深刻了解 &i 的含义之后就可以不那么说了。

2.2、scanf(“输入控制符非输入控制符”, 输入参数);

这种用法现在应该是很少人使用或者基本就没人使用,但是总有头铁的人使用。经常有人问,为什么 printf 中可以有“非输出控制符”,而 scanf 中就不可以有“非输入控制符”。事实上不是不可以有,而是没有必要!

int main(void)
{
    int input;
    scanf("input = %d", &input);
    printf("input = %d\n", input);
    return 0;
}

在 printf 中,所有的“非输出控制符”都要原样输出。同样,在 scanf 中,所有的“非输入控制符”都要原样输入。所以在输入的时候,input= 必须要原样输入。比如要从键盘给变量 input赋值 30,那么必须要输入input=123才正确,少一个都不行,否则就是错误。

在使用scanf函数的时候,没有必要加\n,因为scanf中\n不起换行的作用,它不但什么作用都没有,我们还要原样将它输入一遍。

scanf函数允许一次给多个变量赋值。通过键盘给多个变量赋值与给一个变量赋值其实是一样的。比如给两个变量赋值就写两个 %d,然后“输入参数”中对应写上两个 “取地址变量” ;给三个变量赋值就写三个 %d,然后“输入参数”中对应写上三个 “取地址变量” 。

虽然 scanf 中没有加任何“非输入控制符”,但是从键盘输入数据时,给多个变量赋的值之间一定要用空格、回车或者 Tab 键隔开,用以区分给不同变量赋的值。而且空格、回车或 Tab 键的数量不限,只要有就行。一般都使用一个空格。

当用 scanf 从键盘给多个变量赋值时,scanf 中双引号内多个“输入控制符”之间千万不要加逗号。

最后重要的事情讲三次!scanf“输入参数”的取地址符&千万不要忘了!scanf“输入参数”的取地址符&千万不要忘了!scanf“输入参数”的取地址符&千万不要忘了!初学者很容易忘记这个符号,从而导致程序有问题,但是要记住printf函数的“输出参数”是不带取地址符的。

2.3、字符串和转义字符

字符串是一个或多个字符的序列,比如:“hello world”。

双引号不是字符串的一部分。双引号仅告知编译器它括起来的是字符 串,正如单引号用于标识单个字符一样。

C语言没有专门用于储存字符串的变量类型,字符串都被储存在char类型的数组中。数组由连续的存储单元组成,字符串中的字符被储存在相邻的存储单元中,每个单元储存一个字符。

 数组末尾位置的字符\0。这是空字符。C 语言用它标记字符串的结束。空字符不是数字0,它是非打印字符,其ASCII 码值是(或等价于)0。C中的字符串一定以空字符结束,这意味着数组的容量必须至少比待存储字符串中的字符数多1。字符串看上去比较复杂!必须先创建一个数组,把字符串中的字符逐个放入数组,还要记得在末尾加上一个\0。还好,计算机可以自己处理这些细 节。

那么,什么是数组?可以把数组看作是一行连续的多个存储单元。用更正式的说法是,数组是同类型数据元素的有序序列。后面会详细探讨。

如何利用printf函数和scanf函数输出输入字符串呢?输入控制符和输出控制符都是%s。

%s告诉printf()打印一个字符串,%s告诉scanf()要接收一个字符串类型数据,我们不用把空字符放入字符串末尾,scanf()在读取输入时就已完成这项工作。注意,scanf函数在遇到第一个空白(空格、制表符或换行符)时就不再读取输入,如果我们要输入带有空格的字符串是不能使用scanf函数的,需要用到其他输出函数,后面再讲这些,scanf函数其实已经满足我们初学者大部分的需求

字符串与字符

字符串常量"x"和字符常量'x'不同。区别之一在于'x'是基本类型 (char),而"x"是派生类型(char数组);区别之二是"x"实际上由两个字符组成:'x'和空字符\0。通过下图更好的理解:

 在初次认识字符串的时候必不可少要认识的就是strlen这个字符串相关函数

strlen函数的原型:

size_t strlen ( const char * str );

strlen函数作用:计算字符串 str 的长度,直到空结束字符,但不包括空结束字符。

这个函数的作用好像和关键字sizeof的作用很相似,但实际上是有很大的区别的,下面来一个测试案例来直观的认识这一点:

 对于我们手动输入的字符串hello,两种方式得出的结果却不相同,这是为什么呢?sizeof就是一个计算数据类型所占空间大小的单目运算符,在计算字符串的空间大小时,包含了结束符\0的位置;而strlen是一个计算字符串长度的函数,使用时需要引用头文件#include <string.h>,不包含\0,即计算\0之前的字符串长度。

当我们使用printf函数输出一些字符串的时候,会发现一些“错误”情况,例如:

会发现我们预想的情况对比实际情况少了一个\和\t,这是为什么呢?

这是因为有转义字符的存在,转义字符顾名思义就是转变意思。转义字符以\或者\x开头,以\开头表示后跟八进制形式的编码值,以\x开头表示后跟十六进制形式的编码值。对于转义字符来说,只能使用八进制或者十六进制。

常用转义序列
转义字符意义示例
\a响铃printf("\a");电脑发出蜂鸣
\b退格,将当前位置移到前一列printf("123\b");输出12
\f换页,将当前位置移到下页开头
\n换行,将当前位置移到下一行开头printf("123\n");输出123换行
\r回车,将当前位置移到本行开头printf("123\r456");输出456
\t水平制表printf("\t123");输出8个空格123
\v垂直制表
\\反斜杠printf("\\");输出\
\'单引号printf("\'");输出‘
\"双引号printf("\”");输出“
\?问号printf("\?");输出?
\0oo八进制(oo必须是有效的八进制数,即每个o可表示0~7中的一个数)printf("\077");输出?(?的ASCll值为63)
\xhh十六进制(hh必须是有效的十六进制数,即每个h可表示0~f中的一个数)printf("\x3f");输出?

2.4、注意事项

1、参数的个数一定要对应

和printf函数一样的,“输出控制符” 和 “输出参数” 无论在 “顺序上” 还是在 “个数上” 一定要一一对应。这句话同样对 scanf 有效,即 “输入控制符” 和 “输入参数” 无论在 “顺序上” 还是在 “个数上” 一定要一一对应。

2、输入的数据类型一定要与所需要的数据类型一致

在 scanf 中,对于从键盘输入的数据类型、scanf中“输入控制符”的类型、变量定义的类型,这三个类型一定要一致,否则就是错的。虽然编译的时候不会报错,但从程序功能的角度讲就是错的,则无法实现我们需要的功能。

写一个小案例测试一下:

 但是,很明显我们是输入了a的,为什么变量却显示未初始化呢?

在 scanf 中,从键盘输入的一切数据,不管是数字、字母,还是空格、回车、Tab 等字符,都会被当作数据存入缓冲区。
存储的顺序是先输入的排前面,后输入的依次往后排。按回车键的时候 scanf 开始进入缓冲区取数据,从前往后依次取。

但 scanf 中 %d 只识别“十进制整数”。对 %d 而言,空格、回车、Tab 键都是区分数据与数据的分隔符。当 scanf 进入缓冲区中取数据的时候,如果 %d 遇到空格、回车、Tab 键,那么它并不取用,而是跳过,继续往后取后面的数据,直到取到“十进制整数”为止。对于被跳过和取出的数据,系统会将它从缓冲区中释放掉。未被跳过或取出的数据,系统会将它一直放在缓冲区中,直到下一个 scanf 来获取。

但是如果 %d 遇到字母,那么它不会跳过也不会取用,而是直接从缓冲区跳出。所以上面这个程序,虽然 scanf 进入缓冲区了,但用户输入的是字母 a,所以它什么都没取到就出来了,而变量 input 没有值,即未初始化,所以输出就是 –858993460。

但如果将 %d 换成 %c,那么任何数据都会被当作一个字符,不管是数字还是空格、回车、Tab 键它都会取回。

3、使用 scanf 之前使用 printf 提示输入

程序写好之后,编译、链接、执行,然后弹出运行窗口,出现一个光标在那不停地闪。对于编写程序的人来说他知道要输入什么,但是对于用户而言,用户怎么知道是什么意思呢?所以之前的程序都缺少提示信息!因此在使用scanf之前,最好先用printf提示用户以什么样的方式输入,这样可以大大提高代码的质量。