C语言深入标准输入输出

了解标准输入输出

所谓标准输入输出,我们先理解为通过控制台来输入和显示内容,只要是个操作系统都会有控制台,这是最基本的人机交互方式,后面会详细讲解标准输入输出的概念。标准输入输出是C语言初学者第一个要越过的门槛,特别是printf()和scanf()这两个格式化输入输出函数,由于格式定义复杂导致很多人望而却步,其实标准输入输出函数并不是C语言定义的标准,而是stdio库中定义的函数,每个编译器实现不尽相同,但差别不大,掌握它们之后再学习文件的输入输出就很简单了,它们的思路是一样的。

printf()

对于格式化输入输出来说,printf()相对简单,因为没有涉及底层硬件,可以将焦点放在格式定义上,下面一起来看看printf()函数的定义:
printf(“格式”, 参数1,参数2…)
如果没有格式控制符,printf()直接输出内容,例如:printf(“Hello World\n”),在格式中使用%开头的字母引用后面的变量,称为格式符,格式符是格式输入输出函数的核心,首先来看看整数:
%d十进制整数 %o八进制整数 %x十六进制整数 %u无符号整数
printf()并未提供八进制和十六进制的有符号输出,也就是说不同进制都是无符号的,另外short用%hd表示,long用%ld表示,long long用%lld表示 long double用lf%表示,如果类型不匹配printf()会自动进行进行类型转换,转换失败会报错。通常用%d来输出short和long不会导致错误,但是会出现警告。%f可以表示单精度和双精度浮点,因为printf()会设法输出足够的长度,不过养成类型严格匹配是一个好习惯。浮点默认有效数字为单精度7位,如果是双精度值会被四舍五入,printf()未提供单独的双精度类型,使用"%lf","%lg"输出精度仍然为7位,但可以通过定义浮点精度解决,格式为%m.nf,m为整数位数,n为小数位数,由于双精度值小数位数不会超过15位,整数不会超过20位,因此20.15%f足以表示双精度。现在我们使用下面代码输出不同的数值类型:

#include<stdio.h>

int main()
{
   
	short a=1;
	int b=2;
	long c=3;
	unsigned d=4;
	double e=3.141592654;

	printf("%hd,%d,%ld,%u,%f,%1.2f,%20.15f",a,b,c,d,e,e,e);

	return 0;
}

bool是新的类型,之前printf()并未定义,因此使用%d输出,ture输出1,false输出0,下面代码输出bool类型的数值:

#include<stdio.h>
#include<stdbool.h>

int main()
{
   
	bool a=true;
	bool b=false;
	printf("a=%d,b=%d",a,b);
	return 0;
}

字符类型使用%c,字符串类型使用%s,使用下面代码测试输出字符和字符串:

#include<stdio.h>
#include<stdbool.h>

int main()
{
   
	char a='a', b='b', c='c';
	char s[10]="Hello";
	printf("%c%c%c %s",a,b,c,s);
	return 0;
}

内存地址通常用十六进制表示,而且长度相同,因此printf()提供了一个新的格式符”%p”,它使用固定的长度输出内存地址,这与某些十六进制文本编辑器显示的结果相同,请看下面的代码:

#include <stdio.h>

int a=1;
int main()
{
   
	printf("%x\n",&a);
	printf("%p",&a);
	return 0;
}

显示结果为:
403010
0000000000403010
通过%p可以看出是64位系统下的内存地址,因为64位系统使用8个字节标记内存地址,在十六进制中FF值为256,因此两位数表示一个字节,8字节一共16位数字。其实这里看到的地址也是虚拟地址,并不等于数据在物理内存中的真实地址,虚拟内存地址是现代计算机因内存管理的需要才提出的概念,一个程序的内存实际上由多个物理内存碎片组成,还包括在磁盘中的虚拟内存,虚拟内存使得应用程序认为它拥有连续的内存空间,这样有助于代码调试。

ANSI C确定了最基本的格式控制符,C99和C11在此基础上增加了一些,所有格式符如下表:
在这里插入图片描述所有数据修饰符如下表:

在这里插入图片描述可以看到,printf()不能输出二进制,通过数字指定的宽度是最小宽度而不是绝对宽度,因此我们可以使用printf(“%06x”, 0x00FF);输出一个字节的十六进制整数,但是不能输出二进制整数,二进制必须使用itoa()转化为字符串输出。printf()具有返回值,如果成功输出返回输出字符个数,如果输出错误会返回一个负值。

scanf()

scanf()比printf()难掌握是因为它要与键盘进行交互,而且涉及到键盘缓冲区的硬件知识,还有按变量地址传入的方式涉及到指针,控制台均以字符串形式传入,因此又要将字符串转换为对应的数据类型。我们首先看看scanf()的格式,再从最简单的问题入手。scanf()函数的格式为:
scanf (“格式”, 地址1,地址2…)
首先scanf()要求传入变量地址而不是变量名,为什么呢?这是因为与printf()不同的是scanf()是赋值操作,也就是需要修改变量的值,而变量可能是数值也可能是字符串,按值传递还是按引用传递不确定,因此统一传入地址。输入整型写为:scanf(“%d”, &a); 输入字符串为:scanf(%s, arr);

第二个问题是内容匹配,在scanf()中输入的格式要和定义的格式完全一致,例如scanf(“a=%d”,&a),必须输入a=1才行,否则会导致scanf()获取不到正确信息进入死循环或宣告失败。当有多个变量需要赋值时,当匹配第一个格式符遇到空格、制表符这样的间隔符或者因为类型不同匹配失败后会继续给下一个匹配,如果缓冲区已没有数据则要求用户继续输入而不退出。例如scanf(“%d%d%d”, &a,&b,&c)可以通过输入1 2 3或1 2 3等来连续赋值,当匹配第一个%d时遇到空格会导致匹配终止,接着匹配下一个%d,在匹配下一个整数时如果遇到空格会自动忽略,直到读取到数字。这里不能输入1,2,3,因为逗号被看作非法字符,当给a赋值完后遇到逗号会停止匹配,而转入下一个%d的匹配,而匹配下一个%d时第一个读取的是逗号,这个非法字符不会像空格那样被忽略,又会导致匹配失败而进入下一个匹配,直到找不到匹配的内容宣告失败,结果只给a进行了赋值操作。如果你只输入了1后回车,当a赋值完后发现缓冲区已没有数据,scanf()会继续让用户输入,当输入2后开始匹配第二个%d,此时键入的回车会被当做空白忽略而正确读到数字2,同样接着输入3可以完成所有的赋值操作,因此输入多个数值时可以使用空白也可以使用回车作为空白间隔符,如果你一定要使用逗号作为间隔符需要改为:scanf(“%d%,d,%d”, &a,&b,&c)。

第三个问题是格式转换问题,标准输入输出操作的都是字符,控制台将所有类型转化为字符后进行输出,键盘键入的内容也是字符,因此在匹配类型的过程中本身就存在数据类型转换。scanf()可以识别包含回车键在内的所有字符,空格、制表符和回车会被当做空白间隔符,printf()是将所有类型转换为字符输出,scanf()是将字符解析为数值,而将字符转化为数值相对困难,出错的机率更高。例如要求输入整数但你输入一个小数,小数点会被当做非法字符导致匹配结束。因此scanf()不仅要求输入内容的格式与给出的格式完全相同,还要求数据类型、甚至小数精度都严格匹配,比如short必须使用%hd,长整数必须使用%ld,双精度必须使用%lf,地址必须使用%p。即便这样,也可能产生意外结果,那就是对于字符串输入,可能你认为输入一个带空格的字符串是一件正常的事情,但却和你想象的不同,scanf()遇到空格就认为字符串输入结束了,因此scanf()是不能接受带空格的字符串的。

最后一个问题是缓冲区,这个问题最频繁也最复杂,因为它涉及到硬件的工作原理,如果不明白缓冲区运行机制,随时会被一些莫名其妙的问题缠住,要解答这个问题,我们只能从硬件知识入手。缓冲区是为解决输入输出速度不对等的性能问题而设置的区域,由于不同的硬件运行速度不同,并且有些硬件访问需要等待的时间开销很大,响应缓慢,缓冲区既可以改善硬件之间的速度不对等问题,又可以减少硬件的访问次数,还可以实现异步工作。速度不对等的交流比较典型的是内存和硬盘,硬盘读写速度远远小于内存,特别是机械硬盘,平时没有读写时处于睡眠状态,当需要访问时要经过请求访问、电机加速、磁头读写、再由主板南桥芯片负责将数据传输到内存等一系列复杂的操作,可以直接导致程序卡顿,例如将休眠的硬盘唤醒读取数据时非常明显。如果在内存中开辟一个缓冲区,一次读取多个数据,由于后续的数据已经在缓冲区,就不必再从硬盘取出了,直接从缓冲区取出,解决了响应慢的问题,这就是所谓的预读。如果需要写入数据,将内容先写入缓冲区,等待缓冲区填满再一次性写入硬盘,这样可以大大减少写入次数。一些读写异常缓慢的硬件干脆采取异步传输,比如打印机,它依赖缓冲区储存临时数据,下载和打印时cpu可以做其它事情从而减少等待时间,这些异步硬件是不会造成计算机卡顿的。现在的计算机硬件,如显卡、声卡、网卡等几乎都会在内存中开辟自己的缓冲区,硬件内部也有缓冲区,而且还将缓冲区分级,例如cpu、硬盘内部都按照速度不同分为一二三级缓存,它们在硬件架构内部自成体系。在工作时硬件内部的缓冲区对接内存中的缓冲区,大大降低了硬件访问次数,缓解了输入输出不对等的问题。我们再来看看软件,其实软件的运行也离不开缓冲区,抛开操作系统在底层给我们创造的缓冲区,我们自行编写软件时也会将频繁读写的数据放到内存中,或者将可能用到数据提前从硬盘取出,或者在程序空闲时将缓冲区的数据写入硬盘,这么一来缓冲区就分为硬件缓存、系统

  • 1
    点赞
  • 12
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值