学习小发现-04-如何从字符串中提取数字并转换为整型输出、如何在%d输入内容中判断整型并只读取数字以整型输出、scanf的各种用法
学习小发现 - 03 - 如何判断输入的是不是整型数据并重新输入,纠错(异常)scanf与strcmp,_阿秋的阿秋不是阿秋的博客-CSDN博客
接续上面这篇文章的问题。
我先说明一下吧,这玩意我主要写的是思考过程,如果想直接看结果,点目录里的成果就行,我觉得这个也没什么实际意义,就是自己突发奇想了一下。也许会有更简便的方法或者函数,但是我觉得思考的过程还是很重要的。
目录
如何在%d输入内容中判断整型并只读取数字以整型输出:
成果
如何从字符串中提取数字并转换为整型输出
成果
scanf的多种用法
scanf如何规定读取个数
scanf如何清空缓冲区
scanf如何指定读取特定字符
scanf如何设置不想读取的字符
scanf如何丢弃读取的字符 - (清理缓冲区)
总结:
如何在%d输入内容中判断整型并只读取数字以整型输出:
已有的代码
int main()
{
printf("请输入数字:>");
int age = 0;
int tmp = 0;
tmp = scanf("%d", &age);
while (tmp == 0)
{
while (getchar() != '\n');
printf("请输入数字:>");
tmp = scanf("%d", &age);
}
return 0;
}
这个代码只能第一个输入字符不是数字字符,便清空缓冲区,重新输入的效果,但是没办法达到只输入一次并读取数字的效果。
根据这个想法,我写了下面这段代码,能够达到只输入一次并只读取数字的效果。
int main()
{
printf("请输入数字:>");
int age = 0;
int ret = 0;
int tmp=0;
scanf("%d", &age); //scanf不会读取缓冲区的\n
//如果缓冲区只剩下\n意味着,前面的都是%d形式的内容
while ( ( ret=getchar() ) !='\n' ) //用getchar判断是否读取完毕,未读取完进入循环
{
if (ret < '0' || ret>'9') //若读取不是0-9内的字符,便继续读取
continue;
tmp = scanf("%d", &age);
}
printf("数字是%d\n", age);
return 0;
}
但是,问题又来了,因为getchar是一个字符一个字符读取,且读取后的字符不会在缓冲区内,所以只会读取第一个数字字符后的字符,也就是9。不信你看下面这个,有789,却只读取了89.
然后我就在想,能不能把这个数字放回缓冲区里,就学到了ungetc这个函数可以把数字放回缓冲区原来的位置。可以达到输入8789,读取8789的效果了。
但是问题又来了,如果第一个就是数字字符,每个数字字符之间都有其他字符,那会发生什么?
哦~只会读取最后一个数字字符,
因为getchar是一个字符一个字符地读取啊。
而scanf是遇到不符合格式的内容便不再读取,所以便只读取了8。
我就又在绞尽脑汁地想,那把每次getchar读取到的数字字符存放到一个地方,然后在统一拿出来按照整型方式输出不就可以了。
但是应该存到哪里去呢?
我想到了数组,因为数组是同一类型元素的集合,便正好适合保存我们读取到的数字字符。
于是便写出了这段代码。
int main()
{
printf("请输入数字:>");
int age = 0;
int tmp = 0;
int ret = 0;
int i = 0;
int j = 0;
int age_tmp[100] = { 0 };
tmp = scanf("%d", &age); //scanf不会读取缓冲区的\n
//如果缓冲区只剩下\n意味着,前面的都是%d形式的内容
while ( ( ret=getchar() ) !='\n' ) //用getchar判断是否读取完毕,未读取完进入循环
{
if (ret < '0' || ret>'9') //若读取不是0-9内的字符,便继续读取
continue;
ungetc(ret, stdin); //stdin可以理解为缓冲区
scanf("%d", &age_tmp[i++]);
//后置++为了方便下个字符放进来
}
//以整型方式输出
age = 0;
for (j = 0; j < i - 1; j++)
{
if (!age)
age = age_tmp[j] * 10 + age_tmp[j + 1];
else
age = age * 10 + age_tmp[j + 1];
}
printf("数字是%d\n", age);
return 0;
}
先演示一下效果,
可以达到从其中提取数字并以整型方式输出的效果了!
问题1:但是,有个致命问题,如果第一个输入的是整型,那么被读取到的整型不会再进入循环。
问题2:还有一个问题,如果输入的字符是连续的两位整型(比如89),那么在循环中读取时候,就是把89放到了一起,就不符合我们后面设定的整形转换输出方式了。
就像下面这样。(第二个问题的图我就不放了)
那么就需要多两个步骤了,1-把读取到的字符单个放到数组中,2-把循环中的scanf改为一次只读取一个整型,这不就好了。
说起来,你没发现我改代码的时候,一直有个tmp变量没用到吗?它接收的是scanf的返回值,也就是scanf按照要求格式读取成功的字符个数。
那这个变量就很适合我们用了。
在写的过程中,我觉得太长了,便直接把这个判断过程写到了函数中。起名叫 judge函数。那么主函数区域就剩这些了。
int main()
{
printf("请输入数字:>");
int age = 0;
int tmp = 0;
tmp = scanf("%d", &age); //scanf不会读取缓冲区的\n
//如果缓冲区只剩下\n意味着,前面的都是%d形式的内容
judge(tmp, &age);
printf("数字是%d\n", age);
return 0;
}
顺便一提:我觉得tmp这个变量接收的是scanf的返回值还是很有用的,不能直接根据age的值来判断有没有读取成功,因为age可以是任何数字0,8,9,98……
成果
现在我们写好了,函数递归部分看不懂的点这个链接。看代码演示那个标题。
从0开始学c语言-14-关于(3)函数递归、递归与迭代、栈溢出、练习求第n个斐波那契数、用递归思想求字符串的长度_阿秋的阿秋不是阿秋的博客-CSDN博客
int transfer(int* age_tmp, int age)
{
//其实就是输入123并输出1、2、3到数组中
int i = 0;
if (age > 9)
{
i = transfer(age_tmp, age / 10); //接收下标
}
age_tmp[i] = age % 10;
return ++i;
}
void judge(int tmp, int* age)
{
int ret = 0;
int i = 0;
int j = 0;
int age_tmp[100] = { 0 };
//如果读取成功(tmp为真),把读取数字 [单个] 放到数组中
if(tmp)
i = transfer(age_tmp, *age); //我写成了函数,并且用i接收函数返回的数组下标
//读取后面部分
while ((ret = getchar()) != '\n') //用getchar判断是否读取完毕,未读取完进入循环
{
if (ret < '0' || ret>'9') //若读取不是0-9内的字符,便继续读取
continue;
ungetc(ret, stdin); //stdin可以理解为缓冲区
scanf("%1d", &age_tmp[i++]); //每次只读取一个%d
//后置++为了方便下个字符放进来
}
//整型方式输出
*age = 0;
for (j = 0; j < i - 1; j++)
{
if (!*age)
*age = age_tmp[j] * 10 + age_tmp[j + 1];
else
*age = *age * 10 + age_tmp[j + 1];
}
}
int main()
{
printf("请输入数字:>");
int age = 0;
int tmp = 0;
tmp = scanf("%d", &age); //scanf不会读取缓冲区的\n
//如果缓冲区只剩下\n意味着,前面的都是%d形式的内容
judge(tmp, &age); //一定要取地址
printf("数字是%d\n", age);
return 0;
}
如何从字符串中提取数字并转换为整型输出
有了上面的基础,便只需要搞清楚字符串中的数字和ASCII码值有什么关系变可以做到我们想要的效果了。
你会发现,这个东西减去0字符的码值便正好是他本身的数字。
然后就写出来这段代码了。
成果
#include <stdio.h>
#define len 100
int main()
{
char A[len];
int B[len] = { 0 };
int i, j,sum= 0;
//输入获取字符数组A
gets(A);
//将A中的数字按照顺序放到B中,并记录放进去了几个数
j = 0;
for (i = 0; i < len; i++)
{
if (A[i] >= '0' && A[i] <= '9')
{
//放到A中的是ASCII码值,减去'0'会正好是这个int数字
B[j++] = A[i] - '0';
//后置++为了让下一个的下标正好对应要放进来的数字下标
}
}
//假设我们输入字符串是asd7jj9k8
//那么现在放到B数组中的是7,9,8
//分别对应下标0,1,2
//此时的j下标是3,正好是放进来数字的个数
//要以整型方式进行输出
for (i = 0; i < j - 1; i++)
{
if (!sum)
sum = B[i] * 10 + B[i + 1];
else
sum = sum * 10 + B[i + 1];
//798
//sum=0时
//sum=7*10+9=79
//sum=79*10+8=798
//或者从最后一位运算也行
}
printf("%d\n", sum);
}
简单吧,有了上面的基础以后就变得超级简单。
顺便一提,其实在探索的过程中我还去学了scanf的用法,发现了新天地,这里放着保存一下。
scanf的多种用法
scanf如何规定读取个数
scanf("%2d", &age);
其他形式也是在%后加数字即可。
scanf如何清空缓冲区
没有清空缓冲区的代码
int main()
{
int a = 0;
char ch[20] = { 0 };
printf("请输入数字");
scanf("%d", &a);
printf("%d\n", a);
//若输入不符合格式的内容,应清空缓冲区
printf("请输入字母");
scanf("%s", ch);
printf("%s\n", ch);
}
代码效果
加上这两行代码到上面那段代码注释的地方
scanf("%*[^\n]"); //将\n前面所有字符清空
scanf("%*c"); //将最后剩下的换行符清空
注:这两行代码不能合并起来,如果缓冲区只剩下\n,\n的前面没有其他字符,不符合 将\n前面所有字符清空 的条件。
scanf如何指定读取特定字符
scanf("%[asdf]",ch); //只读取asdf
scanf("%[0-9]", age);//对应 ASCII码值左边不能大于右边
%[a-zA-Z] 读取大写字母和小写字母
%[a-z-A-Z0-9] 读取所有英文字母和十进制数字
%[0-9a-f] 读取十六进制数字
注:这意味着scanf遇到规定外的字符就不再读取。
如:scanf("%[asdf]",ch); //只读取asdf
输入:adfssdasklas
输出:adfssdas
可以看到,即使后面有符合规定范围内的字符 as 也不会被读取到。
scanf如何设置不想读取的字符
%[^\n] 匹配除换行符以外的所有字符,遇到\n就停止读取,达到了读取一行的效果
%[^0-9] 匹配十进制数字以外的所有字符,遇到0-9内的字符就停止读取
%20[^0-9\n] 读取一行不能包含十进制数字的字符串,且长度不超过20
注意第一个代码,这个效果相当于gets函数读取一行的能力。
scanf如何丢弃读取的字符 - (清理缓冲区)
%*d读取一个整型并丢弃
%*[a-z] 读取小写字母并丢弃
%*[^\n] 将\n以外的字符全部丢弃
例子:
scanf("%*d%d",&n);
总结:
scanf完整控制字符串的写法:
% {*} {width} type
{}可不写
type就是读取什么类型的数据
width表示最大读取宽度
*丢弃读取到的数据,可有可无