[英]Why does this code work on gcc 6.3.0 and not 4.9.2?
我有一个用于显示二分搜索的类的游戏。 代码在我的 ide(atom) 和 gcc 编译器(gcc 版本 6.3.0 (MinGW.org GCC-6.3.0-1))上编译和运行没有错误。 我的教授使用的是 Dev C++,他的编译器是 (gcc 4.9.2)。 不幸的是,他的版本编译时不会出错,但在从普通游戏中获取用户输入时也会失败。 我在调试方面不够熟练,无法找出问题所在。 任何帮助表示赞赏,解释将是令人难以置信的。
#include <stdio.h>
#include <stdlib.h>
#include <time.h>
#include <ctype.h>
//--Menu--//
//user input for main menu
int userInput(){
int var;
scanf("%d", &var);
return var;
}
//main menu for the user to decide what version they want to play
void menuMain(){
printf("------------------------\n");
printf("Menu\n");
printf("1 - Normal Game\n");
printf("2 - Efficency Mode\n");
printf("3 - Quick Game\n");
printf("4 - EXIT PROGRAM\n");
printf("------------------------\n");
};
//good bye message
void menuGoodBye(){
printf("---------------\n");
printf("Good Bye!\n");
printf("---------------\n");
}
//gets the users response and capitalizes it
char responseUser(int guess){
char y;
printf("---------------------------------------------\n");
printf("Is it %d?\n",guess);
printf("Please respond with Lower (L), Higher (H), or Correct (C).\n");
scanf("%s",&y);
y = toupper(y);
return y;
}
//--Normal Game--//
//instructions for the user Normal Games
void instructions(int min,int max){
printf("----------------------------------------------------------------------\n");
printf("Pick a number from %d to %d and I will try and guess it.\n",min,
max);
printf("If your number is lower, respond with a L.\n");
printf("If your number is higher, respond with a H.\n");
printf("If I guess correctly, respond with a C.\n");
printf("----------------------------------------------------------------------\n");
}
//uses binary search for efficient gameplay
int efficentGuesser(int min, int max){
return (max+min)/2;
}
//uses random numbers with updated minimums and maximums
int gameGuesser(int min, int max){
int x;
x=rand()%(max+1-min)+min;
return x;
}
//the modular switch for the game
void gameSwitch(int (*method)(int, int),int min, int max){
char response;
int guess;
do{
guess = (*method)(min,max);
response = responseUser(guess);
switch (response) {
case 'L':
max= guess-1;
break;
case 'H':
min= guess+1;
break;
case 'C':
break;
default:
break;
}
}while(response != 'C');
}
//--Quick Game--//
//instructions for the quick game
void instructionsQuickGame(int min, int max){
printf("----------------------------------------------------------------------\n");
printf("Pick a number from %d to %d and I will try and guess it.\n",min,
max);
printf("----------------------------------------------------------------------\n");
}
//search for a quick game
void quickGame(int (*method)(int, int),int x, int y){
int input,guess,min,max, trys;
input= userInput();
clock_t begin = clock();
min = x;
max = y;
do{
guess=(*method)(min, max);
if(input > guess){
min = guess + 1;
}else{
max = guess - 1;
}
trys++;
}while(input != guess);
clock_t end = clock();
printf("Wow,That took me %.8f seconds and %d trys!\n",(double)(end -
begin) / CLOCKS_PER_SEC,trys);
}
//--Modular Block--//
//basic building block for game, you can import functions modularly
void defaultGame(int (*method)(int, int), void (*s)(), void(*instructions)
(int, int)){
int min, max;
min =0;
max =100;
(*instructions)(min,max);
(*s)((*method),min,max);
}
//the actual code that runs
int main(){
srand(time(0));
int response;
do{
menuMain();
response=userInput();
switch (response){
case 1:
//defaultGame(method, what switch, what instructions)
defaultGame(gameGuesser, gameSwitch, instructions);
break;
case 2:
//defaultGame(method, what switch, what instructions)
defaultGame(efficentGuesser, gameSwitch,instructions);
break;
case 3:
//defaultGame(method, what switch, what instructions)
defaultGame(efficentGuesser, quickGame, instructionsQuickGame);
break;
case 4:
menuGoodBye();
break;
default:
printf("Please Pick a number from the menu.\n");
break;
}
}while(response != 4);
return EXIT_SUCCESS;
}
未定义行为 (UB)
char y; ... scanf("%s",&y);
是未定义的行为"%s"
指示scanf()
,以形成至少一个非空格字符的字符串加上一个附加的空字符。 char y
太小了。 @尤金·什。
请考虑以下内容。
scanf(" %c",&y);
其他问题也存在,但以上是一个大问题。
userInput()
和responseUser()
函数有缺陷; 你需要修复它们。
responseUser()
的致命错误是(正如chux在另一个答案中已经提到的那样)是将%s
扫描转换说明符用于单个字符char y
。
这两个函数都忽略输入的任何和所有问题。 他们不检查转换是否成功(来自scanf()
的返回值),而只是假设它是成功的。
由于标准输入是行缓冲的,因此最好逐行读取输入。 (也就是说,当用户键入某些内容时,它只会在他们按下 Enter 键时才传递给程序。)至少有三种方法可以做到这一点( fgets() 、 fgetc() 循环和scanf() ),但是由于OP 正在使用scanf()
,让我们看看如何最好地使用它。
“技巧”是使用转换模式" %1[^\\n]%*[^\\n]"
。 这实际上由三部分组成。 前导空格告诉scanf()
跳过所有空格,包括换行符。 %1[^\\n]
是一个适当的转换,一个不是换行符 ( \\n
) 的单字符字符串。 目标是一个至少包含两个字符的数组,因为一个由字符本身获取,另一个用于字符串结尾的 nul 字符 ( \\0
)。 %*[^\\n]
是一个跳过的转换,这意味着它实际上不会转换任何东西,但会跳过任何非换行符。 因为它被跳过,并且模式中没有文字或非跳过转换,所以它也是可选的。
这种转换模式的想法是前面的空间消耗所有空白,包括输入缓冲区中留下的任何换行符或空行。 然后,我们转换一个单字符的字符串,并跳过该行上的所有其他内容(但不包括行尾的换行符,它作为跳过空格的一部分被下一行使用)。
这是一个示例函数,它返回输入行的第一个字符,转换为大写; 或 EOF 如果发生错误(例如,用户在行首按Ctrl + D以结束标准输入):
int input(void)
{
char first[2];
if (scanf(" %1[^\n]%*[^\n]", first) == 1)
return toupper( (unsigned char)(first[0]) );
return EOF;
}
请注意, toupper()
将字符代码转换为unsigned char
。 (其原因归结为 C 标准没有说明char
是否是有符号类型的事实,但是<ctype.h>
函数,包括isalpha()
等,被定义为采用无符号 char 字符代码。)
以下面的小游戏主菜单程序为例:
#include <stdlib.h>
#include <locale.h>
#include <stdio.h>
#include <ctype.h>
int input(void)
{
char first[2];
if (scanf(" %1[^\n]%*[^\n]", first) == 1)
return toupper( (unsigned char)(first[0]) );
else
return EOF;
}
void normal_game(void) { printf("<normal game played>\n"); }
void efficiency_game(void) { printf("<efficiency game played>\n"); }
void quick_game(void) { printf("<quick game played>\n"); }
int main(void)
{
if (!setlocale(LC_ALL, ""))
fprintf(stderr, "Warning: Your C library does not support your current locale.\n");
while (1) {
printf("------------------------\n");
printf("Menu\n");
printf("1 - Normal Game\n");
printf("2 - Efficency Mode\n");
printf("3 - Quick Game\n");
printf("4 - EXIT PROGRAM\n");
printf("------------------------\n");
switch (input()) {
case '1':
normal_game();
break;
case '2':
efficiency_game();
break;
case '3':
quick_game();
break;
case '4':
exit(EXIT_SUCCESS);
case EOF:
printf("No more input; exiting.\n");
exit(EXIT_SUCCESS);
default:
printf("That is not a valid choice!\n");
}
}
}
如果您编译(启用警告;我在所有版本的 GCC 中使用-Wall -O2
)并运行上面的代码,您可以尝试在输入不正确的选项时它的响应方式,或者在行的开头按Ctrl + D。
请注意,这确实将输入限制为单个字符。
responseUser()
函数现在很容易实现:
int responseUser(int guess)
{
printf("---------------------------------------------\n");
printf("Is it %d (C), higher (H), or lower (L)?\n", guess);
while(1) {
switch (input()) {
case 'C': return 0;
case 'H': return +1;
case 'L': return -1;
case EOF:
printf("No more input, so aborting.\n");
exit(EXIT_SUCCESS);
default:
printf("Please input C, H, or L.\n");
}
}
}
请注意,我们还可以使用case 'C': case 'c': ...
以上,接受小写和大写字母,并从input()
函数中删除toupper()
。
假设您通过允许用户首先设置整数范围来扩展游戏。 所以,我们要读取数字作为输入。 在这种情况下scanf()
的问题是,如果用户键入的不是数字,它会留在缓冲区中。 重复scanf()
调用只会重试转换缓冲区中的相同输入,并且会失败。 (我们可以通过消耗输入直到遇到换行符或 EOF 来清除输入缓冲区,但这往往是脆弱的,导致程序似乎“忽略”输入行的行为,实际上是在处理输入时落后一行。)
为避免这种情况,最好改用fgets()
方法。 幸运的是,这种方法允许我们以多种形式接受输入。 例如:
#define MAX_LINE_LEN 200
int choose_range(int *min_to, int *max_to)
{
char buffer[MAX_LINE_LEN], *line;
int min, max;
char dummy;
printf("What is the range of integers?\n");
while (1) {
/* Read next input line. */
line = fgets(buffer, sizeof buffer, stdin);
if (!line)
return -1; /* No more input; no range specified. */
if (sscanf(line, " %d %d %c", &min, &max, &dummy) == 2 ||
sscanf(line, " %d %*s %d %c", &min, &max, &dummy) == 2 ||
sscanf(line, " %*s %d %*s %d %c", &min, &max, &dummy) == 2) {
if (min <= max) {
*min_to = min;
*max_to = max;
} else {
*min_to = max;
*max_to = min;
}
return 0; /* A range was specified! */
}
printf("Sorry, I don't understand that.\n");
printf("Please state the range as minimum to maximum.\n");
}
}
在这里,我们使用buffer
数组作为缓冲区来缓冲整个输入行。 如果有更多输入, fgets()
函数会返回一个指向它的指针。 (如果没有更多输入,或发生读取错误,则返回 NULL。)
如果我们确实阅读了一行,我们首先尝试转换模式" %d %d %c"
。 %c
转换单个字符,并用作哨兵测试:如果整个模式转换,结果为 3,但在第二个整数之后至少有一个额外的非空白字符。 如果sscanf()
返回两个,则意味着只有两个整数(可能后跟空格,比如换行符),这就是我们想要的。
如果该模式不起作用,我们接下来尝试" %d %*s %d %c"
。 这是类似的,除了现在两个整数之间有一个跳过的转换%*s
。 它可以是任何非空白字符序列,例如to
或..
。 这个想法是,这将匹配5 to 15
和1 .. 100
。
如果这也不起作用,我们尝试" %*s %d %*s %d %c"
。 我相信您已经知道它的用途:匹配from 5 to 15
或between 1 and 100
输入,但忽略单词,只转换整数。
如果指定了范围,则函数本身返回 0,否则返回非零。 你可以这样使用它:
int minimum_guess, maximum_guess;
if (choose_range(&minimum_guess, &maximum_guess)) {
printf("Oh okay, no need to be rude! Bye!\n");
exit(EXIT_FAILURE);
} else
if (minimum_guess == maximum_guess) {
printf("I'm not *that* stupid, you know. Bye!\n");
exit(EXIT_FAILURE);
}
printf("Okay, I shall guess what number you are thinking of,\n");
printf("between %d and %d, including those numbers.\n",
minimum_guess, maximum_guess);
OP 如何为游戏计时也存在潜在问题。
clock()
函数以每秒CLOCKS_PER_SEC
为单位返回经过的 CPU 时间量。 (也就是说, (double)(stopped - started) / (double)CLOCKS_PER_SEC
返回started = clock()
和stopped = clock()
之间的 CPU 时间的秒数。)
问题是它是 CPU 时间,而不是现实世界时间。 它只是程序进行实际计算的持续时间,不包括等待用户输入的时间。
此外,它可能具有相当低的分辨率。 (在大多数系统上, CLOCKS_PER_SEC
是一百万,但值clock()
以大步长返回增量。也就是说,如果您在循环中重复调用它并打印值,在某些系统上它会多次打印相同的值,然后跳转到一个更大的值并在那里停留很长时间,依此类推;通常精度只有百分之一秒左右。)
如果我们想以高精度测量挂钟时间,我们可以在大多数系统上使用clock_gettime(CLOCK_REALTIME)
在Windows上使用一些Windows clock_gettime(CLOCK_REALTIME)
:
#define _POSIX_C_SOURCE 200809L
#include <time.h>
static struct timespec mark;
static inline void wallclock_mark(void)
{
clock_gettime(CLOCK_REALTIME, &mark);
}
static inline double wallclock_elapsed(void)
{
struct timespec now;
clock_gettime(CLOCK_REALTIME, &now);
return (double)(now.tv_sec - mark.tv_sec)
+ (double)(now.tv_nsec - mark.tv_nsec) / 1000000000.0;
}
(如果您不同意“kludge”,请查看所需的代码。)
使用上面的代码(包括您需要从链接到的其他答案中的附加代码,如果您使用 Windows 并且您的 C 库不支持 POSIX.1clock_gettime()
),您可以使用例如测量挂钟
double seconds;
wallclock_mark();
/* Do something */
seconds = wallclock_elapsed();
有了这个,我们可以轻松地测量每场比赛的挂钟持续时间。
但是,如果我们想将其拆分为计算所花费的时间,以及等待/处理用户输入所花费的时间,就会出现问题,因为这些事情在现实生活中可能同时发生。 如果我们想这样做,我们最好切换到例如 Ncurses,这样我们就可以在它们发生时接收每个按键。
总之, clock()
使用不一定是错误的; 这只是人们试图测量什么并将其正确传达给用户的问题。
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.