我有一个游戏让一个类显示二进制搜索。该代码可以在我的ide(atom)和gcc编译器(gcc版本6.3.0(MinGW.org GCC-6.3.0-1))上编译并运行无任何错误。 我的教授正在使用Dev C ++,而他的编译器是(gcc 4.9.2)。不幸的是,他的版本可以编译而不会出错,但是从Normal Game接受用户输入时也会失败。 我没有足够的调试技巧来找出问题所在。 感谢您的任何帮助,并且解释将令人难以置信。
#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;
}
答案 0 :(得分:2)
未定义行为(UB)
char y; ... scanf("%s",&y);
是未定义的行为,因为"%s"
指示scanf()
形成由至少一个非空白字符和附加的组成的 string 空字符。 char y
太小了。 @Eugene Sh.
请考虑以下内容。
scanf(" %c",&y);
也存在其他问题,但是上述问题很大。
答案 1 :(得分:0)
userInput()
和responseUser()
函数有缺陷;您需要修复它们。
responseUser()
中的致命错误是(如另一个答案中已经提到的chux)是将%s
scan conversion specifier应用于单个字符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': ...
来接受大小写字母,并从toupper()
函数中删除input()
。
比方说,您通过允许用户首先设置整数范围来扩展游戏。因此,我们要读取数字作为输入。在这种情况下,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 kludge:
#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.1 clock_gettime()
),则可以使用例如
double seconds;
wallclock_mark();
/* Do something */
seconds = wallclock_elapsed();
这样,我们可以轻松地测量每个游戏的挂钟持续时间。
但是,如果我们想将其划分为用于计算的时间,以及用于等待/处理用户输入的时间,则会出现问题,因为这些事情可能在同一时间发生。现实生活。如果我们想这样做,最好改用例如Ncurses,因此我们可以在每次按键时收到它们。
简而言之,clock()
的使用不一定是错误的;只是要测量什么,并将其正确传达给用户的问题。