我从零开始学习C语言(6)

书接上回:我从零开始学习C语言(5)

3.2 scanf函数

如同printf函数的格式串,scanf函数的格式串特包含普通字符转换说明两部分。本质用法和printf函数转换说明是一致的。

大部分情况下,scanf函数格式串只包含转换说明:

int i, j;
float x, y;

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

输入以下数字

1 -2 0.3 -5.0e3

scanf函数会把上述信息,对应的赋给i、j、x、y。scanf中紧密的写法"%d%d%f%f"是很普遍的,而printf函数格式串很少这么写。

同样,scanf函数的转换说明数量要与输入变量一一对应,否则不能匹配程序会出错。

实际上,scanf函数读数据不是一种理想的方法,因为读简单,但是如果用户输入了非预期的输入,而程序没有针对处理,那么这个程序将无法正常执行

3.2.1 scanf的模式匹配

模式匹配是指,scanf试图把输入的字符组和转换说明相匹配

在scanf函数解析格式串时,它会忽略该数起始部分的空白字符(空格、水平、垂直制表、换页、换行符),所以我们输入数字时,可以放一行或多行输入。

上面的代码,你输入:

1

-2

0.3

-5.0e3

一样是没问题的,这里多个换行符如上述所说,本质是空白字符。

但要注意一点,它会忽略起始空白符,不代表会完全忽略结尾空白符,也就是说,最后的空白符会作为下次scanf函数调用的第一个字符。

scanf函数遵循什么规则来识别整数或浮点数呢?
读入整数时:scanf 函数首先寻找正号或负号,然后读取数字直到读到一个非数字时才停止。
读入浮点数时:scanf 函数会寻找一个正号或负号(可选),随后是一串数字(可能含有小数点),再后是一个指数(可选)。指数由字母e (或者字母E )、可选的符号和一个或多个数字构成。

scanf还有一个特性:当scanf 函数遇到一个不可能属于当前项的字符时, 它会把此字符“放回原处”,以便在扫描下一个输入项或者下一次调用scanf 函数时再次读入

3.2.2 格式串中普通字符

空白字符:格式串中遇到一或多个连续的空白字符时,scanf 函数从输入中重复读空白字符直到遇到一个非空白字符(把该字符“放回原处”)为止。
格式串中的一个空白字符可以与输入中任意数量的空白字符相匹配。(附带提一下,在格式串中包含空白字符并不意味着输入中必须包含空白字符。格式串中的一个空白字符可以与输入中任意数量的空白字符相匹配,包括零个。)(莫非是正则表达式的\s*)

其他字符:当在格式串中遇到非空白字符时,scanf 函数将把它与下一个输入字符进行比较。如果两个字符相匹配,那么scanf 函数会放弃输入字符而继续处理格式串。如果两个字符不匹配,那么scanf 函数会把不匹配的字符放回输入中,然后异常退出,而不进一步处理格式串或者从输入中读取字符。

有个地方提醒:

printf加\n会换行,如果scanf格式串末尾加了\n,scanf函数将跳过空白字符,读取一个整数,然后跳到下一个非空白字符处。像这样的格式串可能会导致交互式程序一直“挂起”直到用户输入一个非空白字符为止。

比如你输入了一个数字,按回车,程序没响应(挂起),一直按回车,只见控制台不停换行,此时只能打个非空白字符,比如打个字母,程序就往后执行了。所以scanf不要加\n。

程序04 分数相加

为了显示scanf 函数的模式匹配能力,考虑读入由用户键入的分数。分数通常的形式为分子/分母 。scanf 函数允许读入整个分数,而不用将分子和分母视为两个整数分别读入。下面的分数相加程序体现了这一方法。

addfrac.c

#include <stdio.h>

int main(void)
{
	int num1, denom1, num2, denom2, result_num, result_denom;
 	printf("输入第一个分数");
 	scanf("%d/%d", &num1, &denom1);
 	printf("输入第二个分数");
 	scanf("%d/%d", &num2, &denom2);
 	result_num = num1 * denom2 + num2 * denom1; //通分-分子
 	result_denom = denom1 * denom2; //通分-分母
 	printf("和是:%d/%d\n", result_num, result_denom);
 	return 0;
}
注意,结果并没有化为最简分数。

其实可以写一个求最大公约数的函数,分子分母同时除以它俩最大公约数。根据得到的结果再显示对应是分数还是整数。

求最大公约数(辗转相除法)(递归形式)

int gcd(int a, int b)
{
	if(b == 0) return a;
	return gcd(b, a % b);
}

编程题

1、编写一个程序,以月/日/年(即mm/dd/yy)格式接受用户录入的日期信息,并以年月日(即yyyymmdd)的格式将其显示出来:

Enter a date (mm/dd/yyyy): 2/17/2011
You entered the date 20110217

分析:

先确定输入:月、日、年。可以定义三个整型变量m,d,y来存;

再确定输出:可以输出这三个变量,只是要注意输出格式

先看输入的格式串应该怎么写:

三个数字中间由斜杠隔开,所以应该是:%d/%d/%d

再看输出的格式串怎么写:

年月日直接输出,但是月份会补0到两位,日我估计也要补0到两位。

所以应该是:%d%2d%2d,好像不对,%2d的2是字段右对齐,差的前面补空格,我要能补0。

再看书,原来%d也有精度:即%m.pd,p指明了待显示的数字的最少个数(必要时会在数前加上额外的零)。所以我输出的格式串就应该为%d%.2d%.2d

代码如下:

#include <stdio.h>

int main(void)
{
	int m, d, y;
	printf("Enter a date (mm/dd/yyyy): ");
	scanf("%d/%d/%d", &m, &d, &y);
	
	printf("You entered the date %d%.2d%.2d", y, m, d);
	
	return 0;
}

2、 编写一个程序,对用户录入的产品信息进行格式化。程序会话应类似下面这样:

Enter item number: 583
Enter unit price: 13.5
Enter purchase date (mm/dd/yyyy): 10/24/2010

Item       Unit          Purchase
              Price        Date
583        $  13.50   10/24/2010

其中,产品编号和日期项采用左对齐方式,单位价格采用右对齐方式,允许最大取值为9999.99的美元。提示 :各个列使用制表符控制。

分析:

输入有5个,整型变量item,浮点型变量price,整型变量m,整型变量d,整型变量y

输出要注意的就是右对齐得用%-nd来表示,既然是9999.99最大值,所以应该是%-7.2f。

列用制表符就用\t来表示。

日期已经有上题的经验了,所以代码如下:

#include <stdio.h>

int main(void)
{
	int item_number, m, d, y;
	float unit_price;
	
	printf("Enter item number:");
	scanf("%d", &item_number);
	
	printf("Enter unit price:");
	scanf("%f", &unit_price);
	
	printf("Enter purchase date(mm/dd/yyyy):");
	scanf("%d/%d/%d", &m, &d, &y);
	
	printf("Item\t Unit\t\t Purchase\n");
	printf("\t Price\t\t Date\n");
	printf("%d\t $%7.2f\t %.2d/%.2d/%d", item_number, unit_price, m, d, y);
	 
	return 0;
}

3、 图书用国际标准书号(ISBN)进行标识。2007年1月1日之后分配的ISBN包含13位数字(旧的ISBN使用10位数字),分为5组,如978-0-393-97950-3。第一组(GS1前缀 )目前为978或979。第二组(组标识 )指明语言或者原出版国(如0和1用于讲英语的国家)。第三组(出版商编号 )表示出版商(393是W. W. Norton出版社的编号)。第四组(产品编号 )是由出版商分配的用于识别具体哪一本书的(97950)。ISBN的末尾是一个校验数字 ,用于验证前面数字的准确性。编写一个程序来分解用户录入的ISBN信息:

Enter ISBN: 978-0-393-97950-3
GS1 prefix: 978
Group identifier: 0
Publisher code: 393
Item number: 97950
Check digit: 3

注意 :每组中数字的个数是可变的,不能认为每组的长度都与示例一样。用实际的ISBN值(通常放在书的封底和版权页上)测试你编写的程序。

分析:有了前面基础,这个简单,格式串直接写成"%d-%d-%d-%d-%d",定义5个变量一存一打印。

#include <stdio.h>

int main(void)
{
	int prefix, id, code, number, check;
	printf("Enter ISBN:");
	scanf("%d-%d-%d-%d-%d", &prefix, &id, &code, &number, &check);
	printf("GS1 prefix: %d\n", prefix);
	printf("Group identifier: %d\n", id);
	printf("Publisher code: %d\n", code);
	printf("Item number: %d\n", number);
	printf("Check digit: %d\n", check);
	return 0;
}

4.、编写一个程序,提示用户以(xxx) xxx-xxxx的格式输入电话号码,并以xxx.xxx.xxxx的格式显示该号码:

Enter phone number [(xxx) xxx-xxxx]: (404) 817-6900
You entered 404.817.6900

#include <stdio.h>
int main(void)
{
    int n1, n2, n3;
    printf("Enter phone number [(xxx) xxx-xxxx]: ");
    scanf("(%d) %d-%d", &n1, &n2, &n3);
    printf("You entered %d.%d.%d", n1, n2, n3);
    
    return 0;
}

5、编写一个程序,要求用户(按任意次序)输入从1到16的所有整数,然后用4×4矩阵的形式将它们显示出来,再计算出每行、每列和每条对角线上的和:

Enter the numbers from 1 to 16 in any order:
16 3 2 13 5 10 11 8 9 6 7 12 4 15 14 1


16 3 2 13
 5 10 11 8
 9 6 7 12
 4 15 14 1


Row sums: 34 34 34 34
Column sums: 34 34 34 34
Diagonal sums: 34 34

如果行、列和对角线上的和都一样(如本例所示),则称这些数组成一个幻方 (magic square)。这里给出的幻方出现于艺术家和数学家Albrecht Dürer在1514年的一幅画中。(注意,矩阵的最后一行中间的两个数给出了该画的创作年代。)

我懒,我想用后面的知识解决这道题:

#include <stdio.h>
int main(void)
{
	int a[4][4] = {0}, i, j;
	int row_sums[4] = {0}, column_sums[4] = {0}, diagonal_sums[2] = {0};
	
	printf("Enter the numbers from 1 to 16 in any order:\n");
	
	for(i = 0; i < 4; i++)
	{
		for(j = 0; j < 4; j++)
		{ 
			scanf("%d", &a[i][j]); 
		}
	}
	 
	for(i = 0; i < 4; i++)
	{
		for(j = 0; j < 4; j++)
		{ 
			printf("%d ", a[i][j]); 
		}
		printf("\n");
	} 
	 
	for(i = 0; i < 4; i++)
	{
		for(j = 0; j < 4; j++)
		{
			row_sums[i] += a[i][j];
			column_sums[j] += a[j][i];
		}
		diagonal_sums[0] += a[i][i];
		diagonal_sums[1] += a[4-1-i][4-1-i];
	}
	
	printf("Row sums: %d %d %d %d\n", row_sums[0], row_sums[1], row_sums[2], row_sums[3]);
	printf("Column sums: %d %d %d %d\n", column_sums[0], column_sums[1], column_sums[2], column_sums[3]);
	printf("Diagonal sums: %d %d", diagonal_sums[0], diagonal_sums[1]);
    return 0;
}

6、 修改3.2节的addfrac.c 程序,使用户可以同时输入两个分数,中间用加号隔开:

Enter two fractions separated by a plus sign: 5/6+3/4

The sum is 38/24

#include <stdio.h>

int main(void)
{
	int num1, denom1, num2, denom2, result_num, result_denom;
 	printf("Enter two fractions separated by a plus sign:");
 	scanf("%d/%d+%d/%d", &num1, &denom1, &num2, &denom2);
 	result_num = num1 * denom2 + num2 * denom1; //通分-分子
 	result_denom = denom1 * denom2; //通分-分母
 	printf("The sum is %d/%d\n", result_num, result_denom);
 	return 0;
}

总结:第三章就是教你怎么玩好C语言的输入输出函数的。当然好多细节要注意到位,不过我印象最深的还是这个%m.pd格式说明符。

参考书籍:《C语言程序设计:现代方法(第2版)》

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Jenkinscao

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值