【C语言入门到精通 03】格式化输入/输出

03 格式化输入/输出

A programming language is low level when its programs require attention to the irrelevant.1

码字不易,对你有帮助 **点赞👍/转发↪️/关注 👀 ** 支持一下作者
微信搜公众号:不会编程的程序圆
看更多干货,获取第一时间更新

请将本片与下一节《数据类型》 联系起来一起“食用”。

注:本教程含有超纲内容!!!如果你看不懂,不要丧失信心,可以“不求甚解”一些,关键是要多写代码!然后继续学习下面的内容!

🗺思维导图


✉️写在前面


如果只是写个人学习总结的博客很容易,简单写一些感悟然后贴上代码走人就可以了,甚至不用校审。但是我命名本系列为【C语言必知必会】帮助你从入门到精通 C语言,那势必要“事无巨细”一些:既要考虑到没有基础的初学者,又不能止于基础。所以本教程适合各类人群学习,只要你看就一定会有帮助。

本教程是本人纯手打并排版,校审由我与我的搭档汤圆君一起完成的。你们看这一篇文章我要写好几个小时。如果文章对你有帮助,请不要“白嫖”。支持一下作者,作者会发更多干货好文。

特别鸣谢:汤圆君(公众号:【Cc听香水榭】 长期更新高质量英语教学)关注她表示对她工作的认可吧!

▶️ 此符号表示该内容以后的章节会讲解,此章节内不要求理解。

🌐目录


printf 函数

printf()函数打印数据的指令要与待打印数据的类型相匹配。例如,打印整数时使用 %d,打印字符时使用 %c 。这些符号被称为转换说明(conversion specification),它们指定了如何把数据(以2进制形式)转换成可显示的形式。

例如:

printf("I am %d years old", 18);

这是 printf()的格式:

printf(格式字符串,待打印项1,待打印项2,...);

待打印项都是要打印的的项。它们可以是变量,常量,甚至是在打印之前计算的表达式。上例中,只有一个待打印项: 18 。

格式字符串包含两种不同信息:

  • 普通字符:以字符串中出现的形式打印出来。上例中,“I am” 与 " years old" 为普通字符
  • 转换说明:用待打印项的值来替换。上例中,"%d" 为转换说明

⚠️

C语言的编译器不会检测格式字符串中转换说明中的数量与待打印项总个数是否相匹配。

1.缺少参数

printf("%d %d\n", i); // wrong

printf 会正确显示 i 的值,然后显示一个无意义的整数值。

2.参数过多

printf("%d\n", i, j);// wrong

而在这种情况下,printf 函数会显示变量 i 的值,但是不会显示变量 j 的值


printf 转换说明

转换说明这部分我做了很久,比较详细,配合下一章数据类型才能看懂大部分,剩下的就需要你在不断使用的过程中领悟了。

  • 标志(可选,允许出现多于一个)

    - 字段内左对齐(默认右对齐)
    + 在打印的数前加上 + 或 - (通常只有负数前面附上减号)例1
    空格 在打印的非负数前前面加空格( + 标志优先于空格标志)例2
    # 对象:八进制数,十六进制数,以g/G 转换输出的数 例3
    0 用前导 0 在字段宽度内对输出进行填充。如果转换格式为d,i,o,u,x(X),而且指定了精度,可以忽略 0 例4

    例 1:

    	printf("%d\n", 123);
    	printf("%d\n", -123);
    	printf("%+d\n", 123);
    	printf("%+d\n", -123);
    
    123
    -123
    +123
    -123
    

    例 2:

    printf("% d\n", 123);
    printf("% d\n", -123);
    printf("% +d\n", 123);
    
     123
    -123
    +123
    

    例 3:

    printf("%o\n", 0123);
    printf("%x\n", 0x123);
    printf("%#o\n", 0123);
    printf("%#x\n", 0x123);
    printf("%#g\n", 123.0);
    printf("%g\n", 123.0);
    
    123
    123
    0123
    0x123
    123.000
    123
    

    例 4:

    printf("%5d\n", 123);
    printf("%05d\n", 123);
    printf("%5.3d\n", 123);
    
      123
    00123
      123
    
  • 最小字段宽度(可选)

    如果数据项太小无法达到这个宽度,那么会对字段进行填充。(默认情况下会在数据项左侧添加空格,从而使字段宽度内右对齐)。

    如果数据项过大以至于超过了这个宽度,那么会完整的显示数据项。

    字段宽度可以是整数也可以是字符 *。如果是字符 * ,那么字段宽度由下一个参数决定。如果这个参数为负,它会被视为前面带 - 标志的正数。例5

    例 5:

    printf("%5d\n", 123);
    printf("%2d\n", 123);
    printf("%*d\n", 5, 123);
    printf("%*d\n", -5, 123);
    
     123
    123
     123
    123
    
  • 精度(可选项)

    如果转换说明是:

    d,i,o,u,x,X, 那么精度表示最少位数(如果位数不够,则添加前导 0 )

    a,A,e,E,f,F ,那么精度表示小数点后的位数

    g,G,那么精度表示有效数字个数

    s,那么精度表示最大字节数

    精度是由小数点(.)后跟一个整数或 * 字符构成的。如果是 * ,那么精度由下一个参数决定(如果这个参数为负,效果与不指定精度一样。)如果只有小数点,那么精度为0 。例 6

    例 6:

    printf("%.4d\n", 123);
    
    printf("\n");
    
    printf("%f\n", 123.0);
    printf("%.1f\n", 123.0);
    
    printf("\n");
    
    printf("%g\n", 123.0);
    printf("%.5g\n", 123.0);
    
    printf("\n");
    
    printf("%s\n", "Hello");
    printf("%.2s\n", "Hello");
    
    printf("\n");
    
    printf("%.*d\n", 4, 123);
    printf("%.*d\n", -4, 123);
    
    0123
    
    123.000000
    123.0
    
    123
    123
    
    Hello
    He
    
    0123
    123
    
  • 长度修饰符(可选)。

    长度修饰符表明待显示的数据项的长度大于或小于特定转换说明中的正常值。例7

    长度修饰符 转换说明符 含义
    hh (C99) d,i,o,u,x,X signed char, unsigned char
    h d,i,o,u,x,X short, unsigned short
    l d,i,o,u,x,X long, unsigned long
    ll (C99) d,i,o,u,x,X long long, unsigned long long
    L a,A,e,E,f,F,g,G long double
    z (C99) d,i,o,u,x,X size_t
    j (C99) d,i,o,u,x,X ptrdiff_t

    例 7:

    printf("%#hhX\n", 0xAABBCCDDEEFF1122);//这是一个占用内存为 8 个字节的十六进制数
    printf("%#hX\n", 0xAABBCCDDEEFF1122);
    printf("%#X\n", 0xAABBCCDDEEFF1122);
    printf("%#lX\n", 0xAABBCCDDEEFF1122);
    printf("%#llX\n", 0xAABBCCDDEEFF1122);
    
    0X22
    0X1122
    0XEEFF1122
    0XEEFF1122
    0XAABBCCDDEEFF1122
    
  • 转换说明符

    由于参数提升(▶️),在实参传递给可变数量实参函数时,float 会转换为 double ,char 会转换为 int。例8

    转换说明符 含义
    d,i 把 int 类型转换为 十进制形式
    o,u,x,X 把无符号整型转换为八进制(o),十进制(u),十六进制形式(x,X)。
    f,F (F C99) 把 double 类型转换为 十进制形式,并把小数点放置在正确位置上。如果没有指定精度,那么小数点后显示6个数字。
    e,E 把 double 类型转换为 科学计数法形式。如果没有指定精度,那么小数点后显示6个数字。
    g,G 把double 类型转换为 f 形式或 e 形式。当数值的指数部分小于 -4,或大于等于精度时,会选择以 e 的形式显示。尾部的 0 不显示(除非用#标志),且小数点后跟有数字才会显示出来。
    a,A (C99) 把 double 类型转换为十六进制科学计数法(p计数法)。
    c 显示无符号字符的 int 类型值。
    s 写出由实参指向的字符串。
    p 把 void* 类型转换为可打印的形式。
    n 相应的实参必须是指向 int 型对象的指针。在该对象中存储 …printf 函数已经输出的字符数量,不产生输出。
    % 写字符 %

    例 8:

    printf("%i\n", 123);
    printf("%d\n", 123);
    
    printf("%o\n", 123);
    printf("%u\n", 123);
    printf("%x\n", 123);
    printf("%X\n", 123);
    
    printf("%f\n", 123.0);
    
    printf("%e\n", 123.0);
    
    printf("%g\n", 123.0);
    
    printf("%a\n", 123);
    
    printf("%c\n", 65);
    
    printf("%s\n", "123");
    
    int* a = 2;
    printf("%p\n", a);
    
    printf("%%\n");
    

    输出:为了方便大家观看我已经将输出中的换行删除了

    123
    123
    
    173
    123
    7b
    7B
    
    123.000000
    
    1.230000e+02
    
    123
    
    0x1.e13430000007bp-1021
    
    A
    
    123
    
    00000002
    
    %
    

    printf() 返回值

返回值:传输到输出流(显示器)的字符数,若出现输出错误或编码错误(对于字符串和字符转换指定符)则为负值

返回类型:int

使用场景:检查输出错误。(看输出的字符数是否正确)

#include<stdio.h>

int main(void) {

   int count;
   
   count = printf("Hello!\n");

   printf("%d\n", count);

   return 0;
}

输出:

Hello!
7

打印较长字符串

允许的换行方式:

printf("Hello %s\n", 
       XiaoHuang);//为了让读者知道该行未完,可以使用缩进

错误的换行方式:

printf("Hello 
       %s\n", XiaoHuang);

如果想在双引号括起来的格式字符串中换行,应该这样写:

  1. printf("Hello");
    printf (" %s\n", XiaoHuang);
    
  2. printf("Hello\
    %s\n", XiaoHuang);
    
  3. printf("Hello"
    	" %s\n", XiaoHuang);// ANSI C
    

方法1:使用多个 printf 语句

方法2:在要换行的地方加上反斜杠( \ )来断行。但是,下一行的代码必须从该行最左端开始,不然输出会包含你所缩进的空白字符。

方法3:ANSI C 引入的字符串连接。C 编译器会将多个字符串看作一个字符串。

scanf() 函数

我们从键盘输入的都是文本,因为键盘只能生成文本字符:字符,数字和标点符号。如果要输入整数 2014,就要键入2,0,1,4.如果要将其存储为数值而不是字符串,程序就必须要把字符依次转换成数值,这就是 scanf() 要做的。

scanf() 把输入的字符串转换成整数,浮点数,字符和字符串,而 printf() 正好与之相反,把整数,浮点数,字符,字符串转换成显示在屏幕上的文本。

scanf() 与 printf() 类似,也要使用 格式字符串 和 参数列表。scanf() 中的格式字符串表明字符输入流的目标数据类型。两个函数的主要区别在于参数列表中。printf() 函数使用变量,常量和表达式,而 scanf() 函数使用指向变量的指针​(▶️)。这里不需要了解指针,只需要记住一下简单的两条:

用 scanf 读取

  • 基本变量类型的值,在变量名前加上一个&
  • 把字符串读入数组中,不要使用 &

下面的程序演示了这两条规则:

input.c —— 何时使用 &

#include<stdio.h>
int main(void){
    
    int age;
    float assets;
    char pets[30];//字符数组,存放字符串
    
    printf("Enter you age, assets and you favorite pet.\n");
    scanf("%d %f", &age, &assets); // 这里要用 &
    scanf("%s", pets);// 字符数组不使用 &
      
    return 0;
}

⚠️

初学者在使用 scanf 时,在应该写 & 的时候容易忽略 & ,所以每次使用 scanf 的时候一定要格外小心。通常情况下,必要的地方缺少 & 会让程序崩溃(编译器没有警告),但是也有时候程序并不会崩溃,这时候找 bug 可能会让你头痛。


scanf 的 长度修饰符 和 转换说明符 与 printf 几乎相同。主要的区别如下:

  • 长度修饰符 :(可选项)对于 float 与 double 类型,printf() 的转换说明都用 f; 而对于 scanf() ,float 保持不变,double 要在 f 前加长度修饰符 l ,即:lf例 1

    例 1:

    #include<stdio.h>
    
    int main(void) {
    	
    	double a = 3.0;
    
    	scanf("%lf", &a);
    	printf("%lf", a);
    	return 0;
    }
    
  • 转换说明符%[集合]匹配集合中的任意序列;%[^集合]匹配非集合中的任意序列。例 2

    例 2:

    #include<stdio.h>
    
    int main(void) {
    	
    	char str[10];//字符串数组
    
    	scanf("%[123]", str);
    	printf("%s", str);
    
    	return 0;
    }
    //输入:123456abc123
    //输出:123
    
    int main(void) {
    	
    	char str[10];//字符串数组
    
    	scanf("%[^123]", str);
    	printf("%s", str);
    
    	return 0;
    }
    //输入:abc4123a
    //输出:abc4
    
  • 字符 *:(可选项)字符 * 出现意味着赋值屏蔽(assignment suppression): 读入次数据项,但是不会将其赋值给对象。用 * 匹配的数据项不包含在 …scanf 函数返回的计数中。例 3

    例 3:

    #include<stdio.h>
    
    int main(void) {
    	
    	int a = 0;
    
    	scanf("%*d%d", &a);
    	printf("%d", a);
    
    	return 0;
    }
    输入:1 2
    输出:2
    
  • 最大字段宽度:(可选项)最大字段宽度限制了输入项中的字符数量。如果达到最大值,那么次数据项的转换结束。转换开始跳过的空白不计。例 4

    //输入:1234 Hello
    //先猜测一下输出
    #include<stdio.h>
    
    int main(void) {
    	
    	int a = 0;
    	char str[10];
    
    	scanf("%2d%3s", &a, str);
    	printf("%d %s", a, str);
    
    	return 0;
    }
    //输出:12 34
    

进一步思探究 scanf()

在上面了解了 scanf 的基本情况后,我们进一步探究 scanf 函数。

上面的例 2,为何只是输出了 “123”, 我们明明还输入了一组 123,为什么没有输出呢?

scanf 函数如果发生了 输入失败(没有字符输入)或 匹配失败 (即输入字符和格式串不匹配),那么…scanf 会提前返回。返回就意味着这个 scanf 的读入结束。

scanf 返回的又是什么呢?

成功赋值的接收参数的数量(可以为零,在首个接收用参数赋值前匹配失败的情况下),或者若输入在首个接收用参数赋值前发生失败,则为EOF(EOF 的值是 -1)。

在C程序中测试 scanf 函数的返回值的循环很普遍。例如,下面的循环逐一读取一串整数,在首个遇到问题的符号处停止:

while(scanf("%d", &i) == 1){
    ...
}

对于 scanf 部分最开始的程序 input.c

如果我们这样先输入:

18 98.5
diandian

再这样输入:

  18
98.5


    diandian

如果你添加上 printf 语句输出这三项,会发现,这两种输入的输出是一样的。

在寻找起始位置时,scanf 函数会忽略空白字符(white-space character,包括空格符,水平和垂直制表符,换页符和换行符),但是%[ , %c, %n除外。例 5

例 5:

#include<stdio.h>
int main(void) {

	char ch = 'a';
	char str[10] = "hi";

	scanf("%c", &ch);
	scanf("%[123]", str);

	printf("%c %s", ch, str);


	return 0;
}
//输入: b (输入的是:空格 + b,然后按下回车键想接着输入下一个 scanf)
//输出: hi

这个例子除了证明了上面的结论,还说明了:

但是 scanf 函数会忽略最后的换行符,实际上它没有读取它,这个换行符时下一次 scanf 函数读入的第一个字符。

scanf 函数遵循什么规则来识别整数或浮点数呢?

在要读入整数时,scanf 函数首先会寻找正号或负号,然后从读入一个数字开始直到读入一个非数字为止。

当要求读入浮点数时,scanf 函数首先会寻找正号或负号(可选),然后是一串数字(可能含有小数点),再后是一个指数(可选)。指数由一个字母e,可选的符号,一个或多个数字组成。

当 scanf 函数遇到一个不可能输入当前项的字符时,它会把此字符“放回原处”,以便在扫描下一项或下一次调用 scanf 时再次读入。思考下面(公认有问题的)4个数的排列:

1-20.3-4.0e3回车

然后我们用这个 scanf 函数来读入:

scanf("%d%d%f%f", &i, &j, &x, &y);

scanf 会如何处理这组输入呢?

  • %d :读入 1
  • %d :读入 -20
  • %f :读入 .3 (当作 0.3 处理)
  • %f:读入 剩下的输入。但是不读入最后的回车

使用 %s 转换说明,scanf 会读取除了空白字符以外的所以字符。scanf 跳过空白字符并开始读入第一个非空白字符,保存非空白字符直到再遇到空白字符结束。这意味着,scanf 最多只能读取一个单词。无法利用字段宽度使得 scanf 读取多个单词,scanf 会在字段宽度结束或遇到空白字符处停止。scanf 将字符串放入数组时,会在字符串序列末尾加上一个 \0

格式串中的普通字符

  • 空白字符:…scanf 函数格式串中的一个或多个连续的空白字符与输入流中的零个或多个空白字符匹配。

    简单说一下就是,格式串中有空格,输入时你可以不写空格或写多个;格式串中有多个空格,输入时你可以只写一个空格。

  • 非空白字符:看个程序就明白了:

    #include<stdio.h>
    int main(void) {
    	
    	int i, j, k;
    	
    	printf("Enter a date: ");
    	scanf("%d - %d - %d", &i, &j, &k);
    	printf("date: %d - %d - %d", i, j, k);
    
    	return 0;
    }
    
    //输入:
    Enter a date:  2020   - 2-22
    //输出:
    date: 2020 - 2 - 22
    

    空格你可以随便空,换行都可以随便换,但是一定要打 ‘’-’’ 符号。

易混淆的 printf() 与 scanf()

  1. printf("%d", &i);
    

    输出的并不是 i 的值 (而是 i 的地址的十进制数值)

  2. scanf("%d, %d", &i, &j);
    

    scanf 在第一个 %d 读入一个整数后,试图把逗号与输入流中的下一个字符相匹配,如果这个字符不是 ,,那 scanf 就会终操作,而不再读取变量 j 的值。

  3. scanf("%d\n", &i);
    

    printf 函数中经常有 \n ,但是如果在 scanf 格式串结尾放一个 \n 通常会引发你预期之外的问题。

    对于 scanf 函数来说,\n 等同于空格,那么 scanf 就会在流中寻找空白字符,但是我们上面说过,scanf 格式串中的空白字符会与 输入流中的零个或多个空白字符匹配。所以当你输入完成后按下回车,这个回车会与 scanf 中的 \n 匹配,之后你无论打多少回车都不会使 scanf 结束,除非你输入一个非空字符,使 scanf 因匹配失败而退出。

参考资料:《C Primer Plus》《C语言程序设计:现代方法》


本文GitHub已收录,所有教学和练习代码都会上传上去。

https://github.com/hairrrrr/C-CrashCourse

如果对你有帮助,请点一个 star⭐️ 呦~ ​ 感谢!💌

以上就是本次的内容。

如果文章有错误欢迎指正和补充,感谢!

最后,如果你还有什么问题或者想知道到的,可以在评论区告诉我呦,我可以在后面的文章加上你们的真知灼见👁。

关注我,看更多干货!

我是程序圆,我们下次再见。🍂


  1. 任何编程语言在处理无关事务时都是低级语言。 ↩︎

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章