一、printf()
1、基本用法-printf( 格式字符串, 待打印项1, 待打印项2,…);
格式字符串:这些符号被称为转换说明 (conversion specification),它们指定了如何把数据转换成可显示的形式。在%和转换字符之间 插入修饰符可修饰基本的转换说明。
类型匹配:函数打印数据的指令要与待打印数据的类型相匹配。例如, 打印整数时使用%d,打印字符时使用%c。
待打印项:它们可以是变量、常量,甚 至是在打印之前先要计算的表达式。
2、转化说明 与 修饰符(含标记)
转化说明 主要描述了 按什么数据类型输出。如%d 以十进制有符号的整数,%u 十进制无符号整数,%x 以十六进制无符号整数(字符小写,X字符大写) %c单个字符,%s字符串 %p 指针输出。(由于使用%符号来标识转换说明,因此打印%符号约定使用两个%)
修饰符:在%和转换字符之间插入修饰符可修饰基本的转换说明,用于描述 (1)在打印位置的对齐方式(2)长度不够时前导空格或者0 (3)特殊进制添加前缀-#使用0开头打印8进制,0x开头打印十六进制。(4)最小宽度 (5)精度 (6)标识整形长度类型,如h位short长度、hh为char长度类型、l为long长度类型,ll为longlong型长度类型;
修饰符顺序要求: 如果要插入多个字符,其书写顺序应该与表 中列出的顺序相同。不是所有的组合都可行。
下表列出可作为修饰符的合法字符:
3、转化说明与待打印项不匹配分析
格式字符串中的转换说明一定要与后面的每个项相匹配,若忘记这个基 本要求会导致严重的后果。
1、数量不匹配问题
printf(“The score was Squids %d, Slugs %d.\n”, score1);
这里,第2个%d没有对应任何项。系统不同,导致的结果也不同。出现这种问题最好的状况是得到无意义的值,也可能保留系统堆栈中的关键信息,被攻击获取。
2、类型不匹配问题
通常都有多种选 择。例如,如果要打印一个int类型的值,可以使用%d、%x或%o。打印double类型的值时,可使用%f、%e或%g。但是如果不匹配时,如下:
符号不匹配 :一个负数按无符号输出,输出其负数按补码表示的整数。
长度不匹配 :使用短类型输出长类型时,会发生截断。只读取其最低几个和短类型等长的字节(类似除余)。
混淆整型和浮点型:使用整形打印浮点数会失败,而使用浮点数打印整形时,printf会尝试读取按浮点数长度的预期字节长度,如果整形小于预期会出现越界读取,打印出来的数据无意义。
#define PAGES 336
#define WORDS 65618
int main(void) {
short num = PAGES;
short mnum = -PAGES;
printf("num as short and unsigned short: %hd %hu\n", num,num);
// 输出 num as short and unsigned short: 336 336
printf("-num as short and unsigned short: %hd %hu\n", mnum,mnum);
// 输出 -num as short and unsigned short: -336 65200
/* short 大小是2字节使用二进制补码,0~32767代 表正数,32768~65535为负数。
65535表示-1,65534 表示-2,-336表示为65200 */
printf("num as int and char: %d %c\n", num, num);
// 输出 num as int and char: 336 P
/*只看储存336的2字节中的后1字节,截断相当于用一 个整数除以256,
只保留其余数是80,对应的ASCII值 是字符P */
printf("WORDS as int, short, and char: %d %hd %c\n",WORDS,WORDS,WORDS);
//输出 WORDS as int, short, and char: 65618 82 R
return 0;
}
int main(void) {
float n1 = 3.0;
double n2 = 3.0;
long n3 = 2000000000;
long n4 = 1234567890;
printf("%.1e %.1e %.1e %.1e\n", n1, n2, n3, n4);
// 输出 3.0e+000 3.0e+000 3.1e+046 3.6e+266
/* %e转换说 明让printf函数认为待打印的值是double类型8字节。
除了查看n3的4字节外,还会相邻的4字节共8字节单元组合解释成浮点数,
最终得到的结果 是无意义的值 */
printf("%ld %ld\n", n3, n4);
// 输出 2000000000 1234567890
printf("%ld %ld %ld %ld\n", n1, n2, n3, n4);
// 输出 0 1074266112 0 1074266112
/* %ld转换说明打印浮点数会失败,在 里用%ld打印long类型的数也失败,
问题出在C如何把信息传递给函数。具体情况因编译器实现而异 */
return 0;
}
4、printf的参数传递机制
参数传递机制因编译器实现(入栈的顺序关系)而异。要特别注意
上一个举例中的如printf("%ld %ld %ld %ld\n", n1, n2, n3, n4);
int main(void) {
float n1 = 3.0;
double n2 = 3.0;
long n3 = 2000000000;
long n4 = 1234567890;
printf("%ld %ld %ld %ld\n", n1, n2, n3, n4);
// 输出 0 1074266112 0 1074266112
/* %ld转换说明打印浮点数会失败,在 里用%ld打印long类型的数也失败,问题出在C如何把信息传递给函数。
具体情况因编译器实现而异 */
return 0;
}
1、程序把传入的值放入被称为栈(stack)的内存区域。
计算机根据变量类型(不是根据转换说明)把这些值放入栈中。常见从右往左顺序入栈。因此,n1被 储存在栈中,占8字节(float类型被转换成double类型)。同样,n2也在栈中 占8字节,而n3和n4在栈中分别占4字节。
2、控制转到printf()函数读取栈中内容
该函 数根据转换说明(不是根据变量类型)从栈中读取值。%ld转换说明表明 printf()应该读取4字节,所以printf()读取栈中的前4字节作为第1个值。这是 n1的前半部分,将被解释成一个long类型的整数。根据下一个%ld转换说 明,printf()再读取4字节,这是n1的后半部分,将被解释成第2个long类型的 整数。类似地,根据第3个和第4个%ld,printf()读取n2的前半部 分和后半部分,并解释成两个long类型的整数。因此,对于n3和n4,虽然用 对了转换说明,但printf()还是读错了字节。
3、printf("%d %d %d %d ",i++,–i,++i,i++)说明
不同编译器产生不同的结果,原因主要有1:入栈的顺序关系 2、i++和++i操作符和入栈之间的顺序关系。
如使用vscode中测试gcc编译器:先处理完所有自增自减之后再最后统一一次性的入栈(并且如果发生i++或者i–会先缓存值,对应成员入栈时按缓存值计算入栈值,如果是–i或者++i则无缓存 使用最终i结果计算入栈值) ,测试如下:
int main(void) {
int i = 8;
printf("%d,%d,%d,%d\n", ++i, --i, i++, i--);
// 输出 8,8,7,8
/* 1、先处理++/--
i++(i--):先缓存i的原始值,然后i自增(自减)。最后入栈时,用缓存的i的原始值入栈。
++i(--i):i自增,不缓存i的原始值。最后入栈时,是更新后的最终i。
i--: 缓存8,i=7
i++:缓存7,i=8
--i:i=7
++i:i=8(到这一步时i=8,也就是说最后入栈之前i的最终值为8,因此++i和--i的入栈值都是8)
2、最后入栈
第一个入栈值8(存在缓存),第二个入栈值7(存在缓存),第三个入栈值8(此时的i值),第四个入栈8(此时的i值)
所以这时候栈的数据是:8,8,7,8.(从顶到底)。
*/
i = 0;
printf("%d %d %d %d\n", i+1, i++, i+2, ++i);
// 输出 3 1 3 2
/* 先处理++/--:++i:i为1, i++:缓存1,i为2,入栈:第一个为2(此时i值),第二个为3,第三个 为缓存1,第四个为3,
所以这时候栈的数据是 3 1 3 2 (从顶到底)*/
return 0;
}
二、scanf()
1、基本用法–scanf( 格式字符串, 地址1, 地址2,…);
通用终端格式化输入函数,它从标准输入设备(键盘) 读取输入的信息,给变量赋值。
数据分段:scanf()函数使用空白(换行符、制表符和空格)把输入分成多个字段。 在依次把转换说明和字段匹配时跳过空白。只要在每个输入项之间输入至少一个换行 符、空格或制表符即可,可以在一行或多行输入。唯一例外的是%c会读取每个字符,包括空白。
(1)以试读取一个%d举例:
每次读取一个字符*跳过所有的 空白字符,直至遇到第1个非空白字符才开始读取。因为要读取整数,所以希望发现一个数字字符或者一个符号(+或-)。如果找到一个数字或 符号,保存并读取下一个,直至遇到非数字字符,便认为读到了整数的末尾。然后把非数字字符放回输入。这意味着程序在下一次读取输入时,首先读 到的是上一次读取丢弃的非数字字符。如果使用字段宽度,scanf()会在字段结尾或第1个空白字符处停止读取 (满足两个条件之一便停止)。
(2)其他类型说明:
读取输入和用%d 的情况相同。区别在于 scanf()会把更多字符识别成数字的一部分。%x转换说明要求scanf()识 别十六进制数a~f和A~F。浮点转换说明要求scanf()识别小数点、e记数法 (指数记数法)和新增的p记数法(十六进制指数记数法)。
%s 转换说明,scanf()会读取除空白以外的所有字符。scanf()跳 过空白开始读取第 1 个非空白字符,并保存非空白字符直到再次遇到空白。当scanf()把字符串放进指定数组中时,它会在字符序列的末尾加上’\0。
char str[80];
scanf("%s",str); // 输入 test is good<回车>
printf("%s",str); // 输出 test (遇到第一个空白字符结束)
解决读取包含空格的字符串问题,使用扫描字符集合[],多个字符串的输入,并对结束符进行自定义。
char str[80];
scanf("%[^\n]",str); // 输入 test is good
// 使用扫描字符集合[],多个字符串的输入,并对结束符进行自定义
printf("%s",str); // 输出 test is good
return 0;
2、转化说明 与 修饰符(含标记)
scanf()函数所用的转换说明与printf()函数几乎相同。主要的区别是,对 于float类型和double类型,printf()都使用%f、%e、%E、%g和%G转换说 明。而scanf()只把它们用于float类型,对于double类型时要使用l修饰符。表 4.6列出了C99标准中常用的转换说明。
4、格式字符串 包含 普通字符
scanf()函数允许把普通字符放在格式字符串中,除空格字符外的普通字 符必须与输入字符串严格匹配。
如scanf("%d,%d", &n, &m);中的‘,’为普通字符,用户将输入一个数字、一个逗号,然后再输入一个数字。(%d后面紧跟逗号,所以必须在输入一个数字输入一 个逗号);如果为scanf("%d ,%d", &n, &m),第一个%d后包含空格,空白意味着跳过下一个输入项前面的所有空白,因此第一个输入后可以有空格再出现逗号。
scanf("%d,%d", &n, &m);
/* 情况1:第一个%d后面紧跟','
(第一个数输入完后必须经跟‘,’,‘,’到第二个数可以有空格)
输入可以用:123,234<回车>,123, 234<回车>
不能使用的输入:123 ,234<回车> (‘,’前有空格引起第二次读取‘,’失败,m未i输入)
*/
scanf("%d,%d", &n, &m);
/* 情况2:第一个%d后面有空格连接','
输入可以用:123,234<回车>,123, 234<回车>,123 ,234<回车>,123 , 234<回车>
*/
scanf( )的格式字符串中加入\n的问题,当出现scanf("%d\n",&a)写法时,按照“整型数字 回车”的格式读取,读到回车之后,由于缓冲区空白,程序停滞,等待键盘输入,要等再接收到一个非空白符输入之后,该scanf语句才结束,接着才输出。
int a;
scanf("%d\n",&a);/*注意%d后的\n*/
//单纯输入123<回车>后,会继续等待输入,此时继续输入回车无效的,需要再输入一个非空字符,scanf才结束
printf("%d",a);
5、scanf的缓冲区问题
scanf()函数应该只是扫描stdin流,输入的数据都存再stdin的缓冲区,被跳过和取出的数据,系统会将它从缓冲区中释放掉。未被跳过或取出的数据,系统会将它一直放在缓冲区中,直到下一个 scanf 来获取。
![](/qrcode.jpg)
char str1[80];
char str2[80];
char str3[80];
scanf("%s",str1); // 输入 test is good<回车>
printf("%s\n",str1); // 输出 test
scanf("%s",str2); // 无需输入,缓冲区还有数据
scanf("%s",str3);// 无需输入,缓冲区还有数据
printf("%s\n",str2); // 输出 is
printf("%s\n",str3); // 输出 good
对于参数%d:会忽略缓冲区开头的空白符;对于参数 %c直接读取缓冲区的第一个字符(无论这个字符是什么);
int a;
char c;
scanf("%d",&a); // 输入 123<回车>
scanf("%c",&c); // 直接执行了,由于缓存区还有一个'\n'回车符,c直接获取了
printf("%d %d",a,(int)c); // 输出 123 10 (换行\n的asci码为10)
return 0;
为了解决缓存区问题,可以使用fflush(stdin)清空缓存区,或者getchar()吃掉回车,或者下一行%c前加空格丢弃空白字符。
int a;
char c;
scanf("%d",&a); // 输入 123<回车>
fflush(stdin); // 清空缓存区中数据即回车(也可以用getchar()吃掉,或者在下一行%c前加空格)
scanf("%c",&c); // 输入5
printf("%d %d",a,(int)c); // 输出 123 53 (字符5的asci码值53)
6、scanf返回值
scanf()函数返回成功读取的项数。如果没有读取任何项,且需要读取一 个数字而用户却输入一个非数值字符串,scanf()便返回0。
当scanf()检测 到“文件结尾”时,会返回EOF(EOF是stdio.h中定义的特殊值,通常用 #define指令把EOF定义为-1)。我们将在第6章中讨论文件结尾的相关内容 以及如何利用scanf()的返回值。在读者学会if语句和while语句后,便可使用 scanf()的返回值来检测和处理不匹配的输入。