关于缓冲区输入的若干问题
前置
fgets()函数
- 函数原型
char *fgets(char *str, int n, FILE *stream)
可以理解为(char *fgets(“容器的地址”, “容器的大小”, “从哪里读取”))
- 一些需要注意的细节
- 在
fgets()
函数的眼里,换行符’\n’
也是它要读取的一个普通字符
而已。在读取键盘输入的时候会把最后输入的回车符也存进数组里面,即会把’\n’也存进数组里面,而又由于字符串本身会是以’\0’
结尾的。所以在输入字符个数没有超过第二个参数指定大小
之前,你输入n
个字符按下回车输入,fgets()
存储进第一个参数指定内存地址的是n+2
个字节。最后面会多出一个’\n’
和一个’\0’
,而且’\n’
是在’\0’
的前面一个(\n\0
)。
参考资料:fgets()函数的详解-使用技巧-C语言基础_fgets函数用法-CSDN博客
例题
简易计算器
来源:61CDay04
编写一个程序实现一个简单的计算器,能够处理加法、减法、乘法和除法。
输入:用户输入两个数字**(可以带小数)**和一个操作符(+、-、*、/)。
输出:显示计算的表达式及结果。
注意以下几个要求:
- 代码可以直接写在main函数里,不需要提取函数。
- 输入的表达式应当符合格式要求,应当正常录入两个操作数和一个运算符,否则直接退出程序。
- 在进行除法操作时,需要保证除数不为0,若除数为0,则直接退出程序。
运算结果截图如下:
提示:考虑switch结构,选择不同的运算符。
#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>
int main(void) {
char operator;
double num1, num2;
printf("请输入需要计算的表达式:");
scanf("%lf %c %lf", &num1, &operator,&num2);
switch (operator) {
case '+':
printf("计算的结果是:%lf + %lf = %.2lf\n", num1, num2, num1 + num2);
break;
case '-':
printf("计算的结果是:%lf - %lf = %.2lf\n", num1, num2, num1 - num2);
break;
case '*':
printf("计算的结果是:%lf * %lf = %.2lf\n", num1, num2, num1 * num2);
break;
case '/':
if (0 == num2) {
printf("Error:除数为0!\n");
}else {
printf("计算的结果是:%lf * /lf = %.2lf\n", num1, num2, num1 / num2);
}
break;
}
return 0;
}
简易计算器问题(一)
来源:61CDay04
在上面简单计算器题目的基础上,实现以下功能:
1.在进行完一次运算后,询问用户是否继续运算。只要用户输入y/Y就可以继续运算,而不是只能计算一次。
2.当用户的输入有误时,不退出程序,而是要求用户继续输入表达式计算。
参考的程序运行效果图如下:
提示:
可以考虑使用do…while循环结构,配合循环控制变量来解决,代码仍然都写在main函数中即可。
写完代码后,可以测试一下:在询问循环是否继续时输入"yyyyy",会发生什么情况呢?如何解决?
#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>
int main(void) {
char enter[100];
do {
char operator;
double num1, num2;
printf("请输入需要计算的表达式:");
int ret = scanf("%lf %c %lf", &num1, &operator,&num2);
if (3 != ret) {
printf("Error: 输入的表达式格式不合法!请重新输入。\n\n");
while (getchar() != '\n') // 清空缓冲区至行尾,避免影响下次输入
;
continue; // 继续下一次循环
}
/*while (getchar() != '\n') -- 这个循环会读取输入缓冲区中的字符,直到遇到换行符 '\n' 为止。getchar() 函数从标准输入读取一个字符,当读取到换行符时,循环结束。
; -- 这个分号表示空语句,意味着循环体内没有需要执行的操作。循环的唯一目的是读取并丢弃字符。
continue; -- 这个语句表示跳过本次循环的剩余部分,立即开始下一次循环。*/
switch (operator) {
case '+':
printf("计算的结果是:%lf + %lf = %.2lf\n", num1, num2, num1 + num2);
break;
case '-':
printf("计算的结果是:%lf - %lf = %.2lf\n", num1, num2, num1 - num2);
break;
case '*':
printf("计算的结果是:%lf * %lf = %.2lf\n", num1, num2, num1 * num2);
break;
case '/':
if (0 == num2) {
printf("Error:输入的表达式格式不合法!请重新输入!\n");
}
else {
printf("计算的结果是:%lf * /lf = %.2lf\n", num1, num2, num1 / num2);
}
break;
}
//包含对输入“yyyyyyy”这种特殊情况的处理
printf("是否继续计算?(输入‘y/Y’继续,其他任意键结束):");
while (getchar() != '\n'){ //处理缓冲区\n (因为第一次scanf读取num1,operator,num2后,字符输入缓冲区中可能仍然包含前一次输入时按下的回车键 \n)
;
}
fgets(enter, sizeof(enter), stdin); // 使用 fgets 读取整行输入
//下面检查输入的所有字符
int i = 0, valid = 1;
while (enter[i] != '\n') {
if ('y' != enter[i] && 'Y' != enter[i]) {
valid = 0;//valid=0说明do-while程序要退出
break;
}
i++;
}
if (!valid) {
break;//退出do-while大循环
}
printf("\n");//调整格式,无实际含义
} while (1);
return 0;
}
简易计算器问题(二)
来源:61CDay05
实现一个终端交互式的简易计算器,交互的形式大体如下:
要求至少提供四种运算加减乘除,如下:
int add(int a, int b);
int subtract(int a, int b);
int multiply(int a, int b);
float divide(int a, int b);
并且在结束进程时,打印总共执行操作的次数。(也就是这些函数调用的次数)
注意:除法的实现,要求判断除数不为0,并且在除数为0时使用exit表示异常退出进程。
#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>
#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>
int add(int a, int b) {
return a + b;
}
int subtract(int a, int b) {
return a - b;
}
int multiply(int a, int b) {
return a * b;
}
float divide(int a, int b) {
return a / b;
}
int main(void) {
char enter[100];
int count = 0;
do {
char operator;
int num1, num2;
printf("请输入需要计算的表达式:");
int ret = scanf("%d %c %d", &num1, &operator,&num2);
if (3 != ret) {
printf("Error: 输入的表达式格式不合法!请重新输入。\n\n");
while (getchar() != '\n') // 清空缓冲区至行尾,避免影响下次输入
;
continue; // 继续下一次循环
}
/*while (getchar() != '\n') -- 这个循环会读取输入缓冲区中的字符,直到遇到换行符 '\n' 为止。getchar() 函数从标准输入读取一个字符,当读取到换行符时,循环结束。
; -- 这个分号表示空语句,意味着循环体内没有需要执行的操作。循环的唯一目的是读取并丢弃字符。
continue; -- 这个语句表示跳过本次循环的剩余部分,立即开始下一次循环。*/
switch (operator) {
case '+':
printf("结果:%d\n",add(num1,num2));
count++;
break;
case '-':
printf("结果:%d\n", subtract(num1, num2));
count++;
break;
case '*':
printf("结果:%d\n", multiply(num1, num2));
count++;
break;
case '/':
if (0 == num2) {
printf("Error:除数不可以为0\n");
printf("\n总共执行的操作次数为:%d次\n", count);
exit(1);
}
else {
printf("结果:%d\n", divide(num1, num2));
count++;
}
break;
default:
printf("无效的运算符\n");
}
//包含对输入“yyyyyyy”这种特殊情况的处理
printf("是否继续计算?(y / n):");
while (getchar() != '\n') { //处理缓冲区\n (因为第一次scanf读取num1,operator,num2后,字符输入缓冲区中可能仍然包含前一次输入时按下的回车键 \n)
;
}
fgets(enter, sizeof(enter), stdin); // 使用 fgets 读取整行输入
//下面检查输入的所有字符
int i = 0, valid = 1;
while (enter[i] != '\n')
{
if ('y' != enter[i] && 'Y' != enter[i]) {
valid = 0;//valid=0说明程序要退出
break;
}
i++;
}
if (!valid) {
break;//退出do-while大循环的条件
}
printf("\n");//调整格式,无实际含义
} while (1);
printf("总共执行的操作次数为:%d次\n", count);//打印操作次数
printf("\n");
return 0;
}
掷骰子
来源:61CDay05
编写程序模拟掷骰子的游戏(每一次投掷,都投掷两个骰子)。每局游戏的规则如下:
- 第一次掷的时候:
- 如果点数之和为 7 或 11 则获胜;
- 如果点数之和为2、3或12则落败;
- 其他情况下的点数之和称为“目标”,继续投掷两个骰子。
- 在后续的投掷中:
- 如果玩家再次掷出“目标”点数则获胜;
- 如果掷出7则落败;
- 其他情况都忽略,继续投掷两个骰子。
在每一局游戏结束时,程序都要询问用户是否再玩一次,如果用户输入的回答不是 y 或 Y ,那么就结束游戏,程序此时要打印显示胜败的次数。
玩家游玩过程程序的输出,大体如下所示:
You rolled: 5
Your point is 5
You rolled: 7
You lose!
Play again?(y/Y means continue) y
You rolled: 5
Your point is 5
You rolled: 4
You rolled: 4
You rolled: 9
You rolled: 2
You rolled: 7
You lose!
Play again?(y/Y means continue) y
You rolled: 4
Your point is 4
You rolled: 7
You lose!
Play again?(y/Y means continue) n
Wins: 0 Losses: 3
提示(请思考三个问题):
1.肯定需要使用随机数,那么srand设置种子值,这个函数调用应该放在哪里呢?
2.在键盘录入是否继续游玩时,如果键盘输入的是“(空格)y”,会发生什么事情?怎么解决?
3.在键盘录入是否继续游玩时,如果键盘输入的是“yyy”,会发生什么事情?怎么解决?
#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>
#include <time.h>
#include <stdbool.h>
// 掷一次骰子,一次扔两个骰子,返回掷出的结果
int dice_sum(void)
{
int point1, point2 ;
point1 = rand() % 6 + 1;
point2 = rand() % 6 + 1;
printf("You rolled: %d\n", point1 + point2); // 打印两个骰子点数之和
return point1 + point2;
}
// 玩一次游戏,返回true表示赢,false表示输了
bool play_game(void) {
//第一次投骰子
int point = dice_sum();
if (7 == point || 11 == point) {
printf("You win!\n");
return true;
}
if (2 == point || 3 == point || 12 == point) {
printf("You lose!\n");
return false;
}
printf("Your point is %d\n", point); // 打印第一次掷出的结果
// 第一次掷骰子不能决定输赢
//后续投掷
while (1) //死循环,直到输或赢
{
int target = dice_sum();
//输
if (7 == target) {
printf("You lose!\n");
return false;
}
//赢
if (point == target) {
printf("You win!\n");
return true;
}
}
}
int main(void) {
srand(time(NULL));
int win = 0, lose = 0;
char again[100];
do {
play_game() ? win++ : lose++;
printf("Play again?(y/Y means continue):");
//处理输入 (处理输入有多个"yyyyy" 或者 输入"(空格)y" 也可以继续玩游戏的情况)
fgets(again, sizeof(again), stdin);//将输入存入again字符串
int i = 0, valid = 1;
while (again[i] != '\n')
{
if (' ' == again[i]) { //处理 "(空格)y" 这种输入情况,忽略输入的空格
i++;
continue;
}
if (again[i] != 'y' && again[i] != 'Y') {
valid = 0;
}
i++;
}
//do-while大循环结束的条件
if (!valid) { //输入不是y 或者 Y,表示不玩了结束游戏
break;
}
printf("\n");//调整格式
} while (1);
printf("\n**********Wins:%d,Losses:%d**********\n", win, lose);
return 0;
}
注意点
fgets(again, sizeof(again), stdin);
将电脑屏幕的输入存入again字符串
if (3 != ret) { printf("Error: 输入的表达式格式不合法!请重新输入。\n\n"); while (getchar() != '\n') ; continue; }
简易计算器中,
if (3 != ret)
校验输入参数,比如要求输入2个整数
和1个字符
。(1 + 2),结果输成a + b,此时发生错误。
while (getchar() != '\n'
用来清除缓冲区中错误的输入,避免影响以后的输入,比如清除a + b
valid = 1
控制do-while循环的退出
while (enter[i] != '\n') { if ('y' != enter[i] && 'Y' != enter[i]) { valid = 0; break; } i++; } if (!valid) { break; }
while (again[i] != '\n') { if (' ' == again[i]) { //处理 "(空格)y" 这种输入情况,忽略输入的空格 i++; continue; } if (again[i] != 'y' && again[i] != 'Y') { valid = 0; } i++; } if (!valid) { break; }
参考答案
简易计算器(一)
#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>
int main(void) {
double num1, num2;
char operator;
double result;
char flag = 'y';
do
{
printf("请输入需要计算的表达式(例如:1 + 1): ");
int ret = scanf("%lf %c %lf", &num1, &operator, &num2);
if (ret != 3) {
printf("Error: 输入的表达式格式不合法!请重新输入。\n\n");
while (getchar() != '\n')
; // 清空缓冲区至行尾,避免影响下次输入
continue; // 继续下一次循环
}
switch (operator) {
case '+':
result = num1 + num2;
break;
case '-':
result = num1 - num2;
break;
case '*':
result = num1 * num2;
break;
case '/': {
if (num2 != 0) {
result = num1 / num2;
}
else {
printf("Error: 除数为0!请重新输入表达式。\n\n");
continue;
}
break;
}
default:
printf("Error: 无法识别的操作符!请重新输入表达式。\n\n");
continue;
}
printf("计算的结果是:%.2lf %c %.2lf = %.2lf\n\n", num1, operator, num2, result);
printf("是否继续计算?(输入'y/Y'继续,其他任意键结束): ");
while (getchar() != '\n')
; // 清空缓冲区至行尾,避免影响下次输入
scanf(" %c", &flag);
printf("\n");
} while (flag == 'y' || flag == 'Y');
return 0;
}
简易计算器(二)
#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>
#include <stdlib.h>
// 全局变量,用于记录操作次数
int operation_count = 0;
// 函数声明
int add(int a, int b);
int subtract(int a, int b);
int multiply(int a, int b);
float divide(int a, int b);
void print_operation_count();
int main() {
int a, b;
char operator;
char choice;
do {
printf("请输入要计算的表达式(例如,5 + 3): ");
scanf(" %d %c %d", &a, &operator, &b);
switch (operator) {
case '+':
printf("结果: %d\n", add(a, b));
break;
case '-':
printf("结果: %d\n", subtract(a, b));
break;
case '*':
printf("结果: %d\n", multiply(a, b));
break;
case '/':
printf("结果: %.2f\n", divide(a, b));
break;
default:
printf("无效的运算符。\n");
}
// 询问用户是否继续
printf("是否继续? (y/n): ");
scanf(" %c", &choice);
printf("\n");
} while (choice == 'y' || choice == 'Y');
print_operation_count();
return 0;
}
int add(int a, int b) {
operation_count++;
return a + b;
}
int subtract(int a, int b) {
operation_count++;
return a - b;
}
int multiply(int a, int b) {
operation_count++;
return a * b;
}
float divide(int a, int b) {
if (b == 0) {
printf("error:除数为零!\n");
exit(1);
}
operation_count++;
return (float)a / b;
}
void print_operation_count() {
printf("总共执行的操作次数为: %d次\n", operation_count);
}
掷骰子
#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>
#include <stdbool.h>
#include <stdlib.h>
#include <time.h>
// 玩一次游戏,返回true表示赢,false表示输了
bool play_game(void);
// 掷一次骰子,一次扔两个骰子,返回掷出的结果
int roll_dice(void);
int main(void) {
int wins = 0, losses = 0;// 记录胜利和输的次数
char again; // 决定用户是否继续玩
// 设置随机种子,只需要设置一次,所以放在主函数里
srand(time(NULL));
do {
play_game() ? wins++ : losses++;
printf("\nPlay again?(y/Y means continue) ");
// %c前面必须加空格,因为要跳过前面的空格输入
scanf(" %c", &again);
// 清空一次键盘录入后剩余的垃圾数据,避免对下一次输入产生影响
while (getchar() != '\n')
;
} while (again == 'Y' || again == 'y'); // 如果用户输入'Y'或'y',则继续玩
// 游戏结束,打印赢和输的次数
printf("\nWins: %d Losses: %d\n", wins, losses);
return 0;
}
bool play_game(void) {
int point = roll_dice();
if (point == 7 || point == 11) {
printf("You win!\n");
return true;
}
if (point == 2 || point == 3 || point == 12) {
printf("You lose!\n");
return false;
}
// 第一次掷骰子不能决定输赢,于是继续掷骰子
printf("Your point is %d\n", point); // 打印第一次掷出的结果,后续只要掷出它就赢了
while (1) { // 死循环,直到决定出输赢结束循环
int tally = roll_dice(); // 再次掷骰子
if (tally == point) { // 如果点数与之前相同,玩家赢
printf("You win!\n");
return true;
}
if (tally == 7) { // 如果掷出7点,玩家输
printf("You lose!\n");
return false;
}
}
}
int roll_dice() {
int a = rand() % 6 + 1;
int b = rand() % 6 + 1;
printf("You rolled: %d\n", a + b); // 打印两个骰子点数之和
return a + b; // 返回点数之和
}