读书笔记2--C Primer Plus:字符串和格式化IO

一、字符串和格式化输入输出

1、字符串

就是一个或多个字符的序列,比如"Zing went the strings of my heart!",其中这里的双引号不算进去,它只是用来告诉编译器,里面的内容是字符串,是一种语法规范,从而能让C编译器识别处理。

字符串不是C中的基本数据类型(上一节已经详细讲述过基本类型),那么C处理的方法是使用char 类型数组来存储,如下图所示:
在这里插入图片描述
数组末尾的’\0’字符是空字符NULL,是ASCII码中的非打印字符,码值为0,用于标记字符串的结束。C规定字符串必须以此空字符作为结尾,所以存储数组存储容量必须不小于待存储字符串大小+1。(注意:我们写一个字符串常量的时候并不需要手动添加应该空字符,编译器会自动添加,只需要记得内存要多预留一位即可,否则编译器会如下图所示报错!
在这里插入图片描述
在这里插入图片描述
数组变量和单一变量的区别如下图所示:数组其实就是连续的存储单元。
在这里插入图片描述
注意,字符常量’x’和含一个字符的字符串串常量“x”在内存中的区别如下图所示,综合以上,看到这里应该很清楚字符串这种数据了。
在这里插入图片描述

printf打印字符串时使用%s格式,而scanf以%s读取时如果遇到键盘字符中的空格制表符换行符,则会立刻不再从键盘缓冲区读取输入,所以scanf这个函数,其实是读取字符串中的一个单词而已。

关于字符串的应用中,比较常用的是符号常量和const限定符。为什么呢?因为经常可能要在许多地方使用同一个字符串。若采用普通常量的话,程序每个地方都写一遍,当要修改换为别的字符串时,就要一一修改,很麻烦,而如果使用变量的话,虽然不会有这烦恼,但很有可能在程序某个地方被程序改变了。
更好的方法:前面提到的#define str “you are good"中的str就是符号常量,这种常量会在编译时有预处理器把程序中出现str的地方全部替换成"you are good”。这样一来修改更换很方便。还有一种方法,是对变量使用const限定符,此限定符说白了就是只读,这样一来,变量就不会被程序不经意间修改了,比如const str="you are good"也可以达到类似效果。

2、输入输出

C给我们提供的I\O函数主要printf和scanf,前者是输出函数,后者是输入函数,都使用格式字符串和参数列表。

(1)printf函数

该函数将数据按照转换说明符规定的格式读取和翻译数据,并转换成字符串文本存储到缓冲区,当缓冲区满了,或者遇到\n换行符,或者遇到scanf要求用户输入时,就会刷新缓冲区,将缓冲区的数据打印到屏幕上。
使用此函数的格式如下,其参数是不定长的:
在这里插入图片描述

使用printf打印数据时,要使用与数据匹配的格式,比如将整数打印为十进制整数时使用%d,打印字符时使用%c,打印字符串使用%s,否则会产生错误。这些转换说明符,指定了要如何把数据转换成可显示的形式。C的printf函数提供的转换说明符如下所示:
在这里插入图片描述
这些转换说明符非常重要,它们把计算机内存中二进制数据读取,并转换成一系列字符以便于打印显示,是一种翻译过程。比如,整型76在内存中为(0100 1100),若使用%d,则会转换成字符’7’和字符’6’,然后以76显示出来,也就是把0100 1100翻译成十进制整数来显示;如果是%x,这个值又会被翻译为十六进制的4c显示出来;如果是%o,则会被翻译为八进制的114显示出来;而若是%c,则会被当做ASCII码值对应的字符’L’显示出来。可见同样一个整型数据(0100 1100),采用不同的整型打印格式(%d,%o,%x,%c等等),会显示出不同的效果。
但注意,整型数据要使用整型的转换说明符,浮点型的要使用浮点型的转换说明符。否则会出现难以估摸的后果(上一章有提到过)
即便同为整型或同为浮点型,也要注意细节问题:
如下图,short型数据336以%hd和%hu打印没问题,因为336都在有符号short和无符号short范围内,所以正常;而当short型数据-336以%hd打印打印出来就是-336,以%hu打印表示翻译为无符号short整型十进制打印,最高位不再当做符号位,那么-336就会被当做(-336+65536)= 65200打印(为什么?因为-336的补码就是1111 1110 1011 0000,以%hu打印时最高位不再是符号位,直接作无符号整型来读取,那么这个补码值就是65200)。
那么后面几行打印结果页很容易理解了,336以%d打印就是336,以%c打印会被当做(336-256)=80,因为%c是只读一个字节,336(0000 0001 0101 0000)的低字节为( 0101 0000)= 80,按照ASCII码解读,80对应的字符就是’P’。
最后一行打印,不必解说的,类似前面的,很容易理解。只要明白这些转换说明符就是告诉编译器怎么翻译(怎么去理解内存中的二进制数据)就可以了。
在这里插入图片描述
另外,如果混淆浮点型和整型,那么也会出现错误:如下图所示,编译器并不能将整型数据和浮点型数据进行转化,所以用%e打印整型和使用%d打印浮点型都会出现无法预料的错误值。(为什么呢?比如%.1e打印long整型数据n3,printf会认为要翻译的数据double的8字节型,所以会从变量n3地址开始读8个字节,然后按照double的存储格式进行翻译,也就是会把读到的8字节数据拆成指数部分和尾数部分去计算和显示,同理使用%ld打印double n2=3.0,也是类似的会把double里的指数和尾数混为一谈当做一个整型去理解,这种现象的本质原因在于整型和浮点型存储格式截然不同,所以不能交互混用,必须泾渭分明地使用)
在这里插入图片描述

以上这些是基本型的,还有很多组合型,比如%0hd,%+Lf等等,对不同类型数据进一步规范格式。也就是说可以插入修饰符,做一些变体:
在这里插入图片描述
在这里插入图片描述
关于各项功能,如下所示:
字段宽度:如下图所示,设置的字段段度若小于实际数据显示宽度,那么系统会自动增加到符合实际的宽度,这里符号常量字段宽度为3,而%d和%2d都小于3,所以系统自动将其输出为3字段宽度。而如果设置的字段宽度大于实际数据宽度,那么就按设置的来,如%10d,就显示10字段。
在这里插入图片描述
标记:如下图所示,-号表示把数据左对齐;+号只是根据数据是正数还是负数,以在数据前面打印±符号;空格号和+号类似,不同的在于如果数据是正数,那就打印空格,如果是负数,那就打印-符号;#号功能多些,如果想以八进制打印也就是使用%o,那么添加#号就是在数据前多打印一个o字符,如果是以十六进制打印也就是使用%x,那么添加#号就是在数据前面多打印一个ox;0号就是用0填充前面字段。
在这里插入图片描述
.数字:主要用于浮点型数据的格式输出,如下图所示。其实已经很明了了,%f就是以普通形式去打印和现实浮点数,%e就是以指数表示形式进行打印,而且此时字段宽度和小数点位数均按照默认的(即字段宽度=实际字段宽度,小数点后保留位数=实际小数位数)。%4.2f表示小数点后只保留2位,总字段宽度为4位(前面已经讲过字段宽度了,设置的若小于实际的,则printf会按照实际的来)。
在这里插入图片描述
但如果用于打印整数的话,如下图所示,.数字就会表示该最小打印数,也就是打印只能多不能少于此。如果不够则在数字前面补0直到补够最小位数。(注意,因为这种可能会在前面补0的操作,所以0标记和整数的精度一起出现,那么会自动忽略0标记,因为0标记是全部补0,所以干脆就忽略0标记
在这里插入图片描述而如果用于打印字符串的话,如下图所示,则表示打印的最大数量,只能少不能多。比如%24s就是字宽为24,全部全部字符都打印;而%24.5s则表示字宽为24,但最多打印5个字符。
在这里插入图片描述

(2)scanf函数

作为输入函数,从键盘缓冲区读取字符文本,并按照转换说明符进行组合翻译成对应类型数据,然后存储到指针所指向的存储单元。这与printf的操作截然相反。
scanf使用空白(换行符制表符空格)把输入分成多段,如下图所示:键盘输入数据时一次使用空格,ENTER,TAB键来分隔数据。
在这里插入图片描述
但以上情况除了%c,因为%c会把键盘上所有字符都读取,而不管你是什么空格、TAB还是换行,都不生效。关于scanf的转换说明符如下图所示,和printf相差不大,但还是有些区别的:
在这里插入图片描述
修饰符的话也类似,也有区别:
在这里插入图片描述
在这里插入图片描述
scanf函数的读取逻辑是,除%c外,均从第一个非空白字符开始一直读取字符,直到遇到空白字符(TAB、回车、空格)或所读取字符与读取说明符不匹配时结束一次操作。比如,如果以%d读取,而键盘输入顺序是"A230CC",那么先读到的A不会符合要求,就会把读到的A放回缓冲区,结束本次操作,那么下次再读缓冲区的话,还是会首先读到A。而%c的话,只要是字符就都能读能匹配,但只读取一个字符就完成操作,结束工作了,如下图:
在这里插入图片描述

在这里插入图片描述
如上图,程序调用了两次scanf,但运行程序时第一次输入时键盘依次敲入了字符’A’‘1’‘2’‘3’‘B’‘C’,回车后程序就直接运行结束了!可我明明还调用了一次scanf函数,怎么没等待我再次输入呢?(为什么?因为,scanf本质上是在和键盘缓冲区在直接打交道,而与用户才是间接的,scanf每次调用时会查看缓冲区里有没有文本数据,有就读取,没有才会等待!我在第一次的时候一次性敲入了6个字符,而A与%d不符合所以读取失败scanf终止,轮到第二次scanf函数时,此时因为缓冲区里还是有数据(原封不动)所以就不等待用户了,直接读取缓冲区就行了,不够由于还是从A开始读,而A与%d不符合,所以第二次也读取失败)。
不过,如果改一下代码:
在这里插入图片描述
如上图所示,就会发现,两次都读取成功了!首先,因为第一次scanf时我一次性输入很多字符,第一个scanf读的是%c,也就是什么字符都可读那么一定会成功,此时字符A被读走,操作完成;键盘缓冲区只剩下’1’‘2’‘3’‘B’‘C’,所以第二次scanf执行时,由于缓冲区还有数据,所以不等待用户输入而直接读取,数字字符’1’、‘2’、‘3’被以%d格式读走,然后读取停在缓冲区的字符’B’上,因为’B’不是%d要求的数字字符,然后读到的字符序列会被翻译为int整型数据123,存储到变量age里面,操作完成。这样一来,键盘剩余字符’B’‘C’,而字符’B’就是会成为键盘缓冲区的新的首字符。
如果是多个转换说明的scanf,如下图所示:那么会在第1个出错处停止读取输入。
在这里插入图片描述
图中第一个%d遇到的是字符A,显然匹配错误,那么scanf就终止读取了,也就是键盘缓冲区里的东西压根没动,所以printf打印出来的都是错误,注意中间的?不是代表字符打印了,这是编译器自己随机给的或者其他方式用来显示。
那么修改一下代码,可以看到第二次scanf还是从A开始读走,因为是%c方式,只要是字符都能成功匹配,所以读取成功,这样间接再次证明了第一此scanf读取错误后,键盘缓冲区里的数据并没有动,也就是scanf读取错误时会把读到的那个数据放回去,然后终止操作。
在这里插入图片描述
以上就是scanf函数执行的逻辑,总结一下,scanf从缓冲区读取数据,如果缓冲区没有,那么就等待,否则就不会等待用户输入,如果读取说明符和缓冲区字符不匹配,那么就立即终止了,读走的数据会被放回缓冲区,再次调用scanf时会从刚刚那个数据继续开始,而如果读取成功,那么读到的字符序列会按照说明符的要求转换及组合成对应类型数据,然后存到&指示的变量地址里。

另外,若使用%s读取字符串,那么同样会从第一个非空白字符开始,到第一次遇到空白字符时结束一次操作,也就是说you are good!这段字符输入,只能读取到you,即一个单词。即便使用了字段宽度,也无非要么遇到空白字符要么到了字段宽度极限而结束读取,所以结果要么是读了一个单词,或者读了一个不完整的单词!(注意,%s读取到的数据会scanf会自动额外添加\0字符后,再存到你传给它的数组地址里,这样数组里就是一个完成的C字符串了)。

scanf可做格式输入,也就是用户键盘输入的形式必须与scanf规定的一样,否则一但不匹配就会报错终止。如下面函数,用户得输入123,456也就是必须依次敲入数字、逗号、数字才对。当然,空格除外。
在这里插入图片描述
下图程序就是键盘敲入时没有敲逗号,从而匹配错误。所以第一个scanf只有weight读取正确,后面的chr,age都因为逗号不匹配而终止故没有正确数据。
在这里插入图片描述

返回值:scanf会返回成功读取的项数,如果没有读取任何项或者读取匹配错误,那就返回0,而当检测到文件结尾时,则返回EOF(宏定义为-1)

修饰符*:用于代替字段宽度,在printf中如同占位符,可以通过在程序中参数传入来替代*号,这样一来就更加灵活了,因为不像使用字段宽度时那样要在格式中事先指定。
printf中功能:
在这里插入图片描述
scanf中功能:与printf不同,这里表示跳过或忽略该项的传参输出:如下图所示,键盘输入的前两个数据45,12就被略过输出了,直接都第三个55被存储到变量n中。
在这里插入图片描述

至此,关于字符串以及C格式化输入输出的有关项就介绍完毕了。

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值