• C语言趣味代码(三)


    这一篇主要围绕写一个程序---寻找数字 来写,在这篇我会详细和大家介绍基本实现以及它的改良版,还有相关知识的拓展,干货绝对满满。

    1. 寻找数字

    在这一主题下,我们会编写一些代码,来锻炼玩家的反应力,同时起到锻炼右脑的作用,在这里我们会分为两个部分展开介绍实现。

    1.1 寻找幸运数字

    在寻找幸运数字的训练中,程序会从1到9抽掉一个数字然后再进行显示,玩家需要在一定时间内找出这个数字。程序分好几步来编写,首先我们看下面这段代码:

    1. #include
    2. int main()
    3. {
    4. int i;
    5. int dgt[9]={1,2,3,4,5,6,7,8,9};
    6. int a[9]={0};
    7. for(i=0;i<9;i++)
    8. a[i]=dgt[i];
    9. for(i=0;i<9;i++)
    10. printf("%d",a[i]);
    11. putchar("\n");
    12. return 0;
    13. }

    这段代码把数组dgt的所有元素从前往后依次初始化为1,2,3....9,然后将这些元素复制到数组a中并显示出来。第一个for循环把数组的dgt的所有元素的值都赋值给了下标相同的数组a的元素,以1为单位对i的值进行增量操作,同时循环元素的赋值操作,这样就把dgt里的元素赋值到了数组a。复制数组时必须向上面一样,逐一复制每一个元素,不能直接一次性对数组复制。第二个for语句是为了显示已复制的数组a的所有元素的值。

    复制数组时跳过一个值

    在复制数组时如果能跳过一个元素,那它就更接近我们想要的程序了,我们看下面的代码:

    1. #include
    2. #include
    3. #include
    4. int main()
    5. {
    6. int i,j,x;
    7. int dgt[9]={1,2,3,4,5,6,7,8,9};
    8. int a[8]={0};
    9. srand(time(NULL));
    10. x=rand()%8;
    11. i=j=0;
    12. while(i<9)
    13. {
    14. if(i!=x)
    15. a[j++]=dgt[i];
    16. i++;
    17. }
    18. for(i=0;i<8;i++)
    19. printf("%d",a[i]);
    20. putchar("\n");
    21. rerturn 0;
    22. }

    我们运行一下看看:

    数组dgt还和之前代码中的一样,但数组a的元素比之前少了一个,变成了8个。首先我们先生成一个随机值,在下面的while循环来跳过变量x为下标的元素dg[x],把数组dgt复制到数组a,用变量i来遍历数组dgt,用变量j来遍历数组a。在上面的示例中,有随机数决定的变量i的值为1,一开始变量i和变量j的值都是0,负责两个数组的开头元素,只有在if语句的判断成立的时候,程序才会进行复制元素的值,if语句判断不成立时,程序不会进行复制,变量i用于遍历数组dgt,每次通过while语句进行循环时,i的值都会增量,而用于遍历数组a的变量j的值只有在进行了元素的赋值时才会增量。这是因为只有在通过a[j++]=dgt[i]进行赋值之后,j才会增量。理解了这些内容,我们来实现这个程序的代码也就简单了:

    1. #include
    2. #include
    3. #include
    4. #define MAX_STAGE 10
    5. int main()
    6. {
    7. int i, j, stage;
    8. int dgt[9] = { 1,2,3,4,5,6,7,8,9, };
    9. int a[8];
    10. double jikan;
    11. clock_t start, end;
    12. srand(time(NULL));
    13. printf("请输入缺失的数字。\n");
    14. start = clock();
    15. for (stage = 0; stage < 10; stage++)
    16. {
    17. int x = rand()% 9;
    18. int no;
    19. i = j = 0;
    20. while (i < 9)
    21. {
    22. if (i != x)
    23. a[j++] = dgt[i];
    24. i++;
    25. }
    26. for (i = 0; i < 8; i++)
    27. printf("%d ", a[i]);
    28. printf(": ");
    29. do
    30. {
    31. scanf("%d", &no);
    32. } while (no != dgt[x]);
    33. }
    34. end = clock();
    35. jikan = (double)(end-start) / CLOCKS_PER_SEC;
    36. printf("用时%.1f秒。\n", jikan);
    37. if (jikan > 25.0)
    38. printf("反应太慢了。\n");
    39. else if (jikan > 15.0)
    40. printf("还可以。\n");
    41. else
    42. printf("反应神速。\n");
    43. return 0;
    44. }

    我们调试看看效果:

    在上面这个程序中训练次数有10次,程序不接受错误的答案,其中的do...while循环负责读取从键盘输入的答案,斌判断答案是否正确,变量no读取到的值只要不等于之前在复制时跳过的dgt[x],do...while语句就会循环,因此,只要玩家没有输入正确答案,就无法进入下一个问题。10次训练结束后,程序会显示出玩家所用的时间和对玩家的评价(反应快还是反应慢)。

    重新排列数组元素

    在刚才的程序中,数字1~9时按顺序排列显示的,所以我们可以轻易找到缺失的那个数字,因为只要和上一行比较哪里出现了错位就可以了。下面我们把数字的顺序打乱,增加寻找数字的难度:

    1. #include
    2. #include
    3. #include
    4. #define MAX_STAGE 10
    5. int main()
    6. {
    7. int i, j, stage;
    8. int dgt[9] = { 1,2,3,4,5,6,7,8,9, };
    9. int a[8];
    10. double jikan;
    11. clock_t start, end;
    12. srand(time(NULL));
    13. printf("请输入缺失的数字。\n");
    14. start = clock();
    15. for (stage = 0; stage < 10; stage++)
    16. {
    17. int x = rand()% 9;
    18. int no;
    19. i = j = 0;
    20. while (i < 9)
    21. {
    22. if (i != x)
    23. a[j++] = dgt[i];
    24. i++;
    25. }
    26. for (i = 7; i > 0; i--)
    27. {
    28. int j = rand() % (i + 1);
    29. if (i != j)
    30. {
    31. int tmp = a[i];
    32. a[i] = a[j];
    33. a[j] = tmp;
    34. }
    35. }
    36. for (i = 0; i < 8; i++)
    37. printf("%d ", a[i]);
    38. printf(": ");
    39. do
    40. {
    41. scanf("%d", &no);
    42. } while (no != dgt[x]);
    43. }
    44. end = clock();
    45. jikan = (double)(end-start) / CLOCKS_PER_SEC;
    46. printf("用时%.1f秒。\n", jikan);
    47. if (jikan > 25.0)
    48. printf("反应太慢了。\n");
    49. else if (jikan > 15.0)
    50. printf("还可以。\n");
    51. else
    52. printf("反应神速。\n");
    53. return 0;
    54. }

    我们调试看看效果:

    1.2 寻找重复数字 

    现在我们再写一个程序,这次不抽出数字,而是重复显示数字,然后让玩家找到这个重复的数字。显示的数字一共有10个,比找幸运数字时要多,不过一旦习惯了,反而更容易找到,我们来实现一下:

    1. #include
    2. #include
    3. #include
    4. #define MAX_STAGE 10
    5. int main()
    6. {
    7. int i, j, stage;
    8. int dgt[9] = { 1,2,3,4,5,6,7,8,9, };
    9. int a[10];
    10. double jikan;
    11. clock_t start, end;
    12. srand(time(NULL));
    13. printf("请输入重复的数字。\n");
    14. start = clock();
    15. for (stage = 0; stage < 10; stage++)
    16. {
    17. int x = rand()% 9;
    18. int no;
    19. i = j = 0;
    20. while (i < 9)
    21. {
    22. a[j++] = dgt[i];
    23. if (i == x)
    24. a[j++] = dgt[i];
    25. i++;
    26. }
    27. for (i = 9; i > 0; i--)
    28. {
    29. int j = rand() % (i + 1);
    30. if (i != j)
    31. {
    32. int tmp = a[i];
    33. a[i] = a[j];
    34. a[j] = tmp;
    35. }
    36. }
    37. for (i = 0; i < 10; i++)
    38. printf("%d ", a[i]);
    39. printf(": ");
    40. do
    41. {
    42. scanf("%d", &no);
    43. } while (no != dgt[x]);
    44. }
    45. end = clock();
    46. jikan = (double)(end-start) / CLOCKS_PER_SEC;
    47. printf("用时%.1f秒。\n", jikan);
    48. if (jikan > 25.0)
    49. printf("反应太慢了。\n");
    50. else if (jikan > 15.0)
    51. printf("还可以。\n");
    52. else
    53. printf("反应神速。\n");
    54. return 0;
    55. }

     我们调试看看效果:

    程序大部分和我们上面寻找幸运数字的第二段代码相同,除了里面的while语句其他都相同,只要有以下两点不同:数组a的元素数量从8变成了10,用于遍历数组a的for语句的循环次数从8变成了10。

    键盘的输入和操作性能的提升(MS-Windows/MS-DOS)

    寻找幸运数字和找重复数字的程序都是由scanf函数负责读取从键盘输入的数字。对该函数而言,只要回车键(输入键)没有被按下,就无法获得已输入的字符的信息。因此,训练时玩家需要在数字后面按下回车键,这样就增加运动手指的次数,也失去了操作的实时性。即便是每次读取一个字符的getchar函数也同样需要按回车键。我们可以利用编程环境单独提供的函数(C语言标准库中未定义的函数)来解决这个问题。首先我们分成以下两个环境来学习,之后再把他们结合在一起。

    1. MS-Windows/MS-DOS
    2. UNIX/Linux/OS X

    首先要学习的是在MS-Windows/MS-DOS中该如何解决这个问题。此时我们需要用到Visual C++等编程环境中特有的getch函数和putch函数。我们通过下面的代码来学习这两个函数的作用:

    1. #include
    2. #include
    3. #include
    4. int main()
    5. {
    6. int ch;
    7. int retry;
    8. do
    9. {
    10. printf("请按键。");
    11. ch = getch();
    12. printf("\n按下的键是%c,值是%d。\n", isprint(ch) ? ch : ' ', ch);
    13. printf("再来一次吗?(Y/N):");
    14. retry = getch();
    15. if (isprint(retry))
    16. putch(retry);
    17. putchar('\n');
    18. } while (retry == 'Y' || retry == 'y');
    19. return 0;
    20. }

     我们调试看看效果:

    getch函数:获取按下的键

    getch函数用于获取玩家从键盘输入的字符。它与getchar函数的不同之处就在于,无需使用回车键就可以立即获取信息。

    函数名getch
    头文件#include
    格式int getch(void);
    功能直接从键盘读取字符而不回显
    返回值返回到读取到的字符的值

    使用getch函数进行读取时,输入的字符不会显示在画面上。上面的代码用十进制表示getch函数的字符和该字符的编码。通过isprint函数判断读取的字符为不可见字符时,则显示空白字符以代替该字符。当确认是否要再来一次时,也会调用getch函数。因此,只要按"Y"或"y"键就能够进行循环了(也就是说没必要按回车键)。

    putch函数:输出到控制台

    putch函数负责把字符显示在控制台上。函数输出字符串后,字符会立即显示在画面上,因此需要通过fflush函数(用于强制输出)进行清空操作。

    函数名putch
    头文件#include
    格式int putch(int c);
    功能在画面上显示字符c(在一些特殊的编程环境中,如果c时换行符就只换行而不进行返回操作)
    返回值显示成功后返回输出的字符c,错误则会返回EOF

    在上面的代码中只有当ch(询问是否再来一次时输入的字符)是能显示的字符时,才会用putch函数来显示该字符。这是因为,如果输出了换行符和制表符等不可显示的字符时,才会使用putch函数来显示该字符。这是因为,如果输出了换行符和制表符等不可显示的字符,画面就会混乱。另外,输入字符"Y"或者"y"后,程序会一直循环,直到输入"Y"或者"y"以外的字符。

    键盘输入和操作性能的提升(UNIX/Linux/OS X)

    UNIX和Linux通过Curses库来提供getch函数,我们来看下面的代码:

    1. #include
    2. #include
    3. #include
    4. int main()
    5. {
    6. int ch;
    7. int retry;
    8. initscr();
    9. cbreak();
    10. noecho();
    11. refresh();
    12. do
    13. {
    14. printf("请按键。");
    15. fflush(stdout);
    16. ch=getch();
    17. printf("\n\r按下的键是%c,值是%d。\n\r",isprint(ch)?ch:'',ch);
    18. printf("再来一次?(Y/N):");
    19. fflush(stdout);
    20. retry=getch();
    21. if(isprint(retry))
    22. putchar(retry);
    23. putchar('\n');
    24. fflush(stdout);
    25. }while(retry=='Y'||retry=='y');
    26. endwin();
    27. return 0;
    28. }

    因为我才开始学习Linux,还不会在Linux环境下运行这段代码,这里运行结果等后面再补上。这段代码只能在提供Curses库的环境下使用(MAS的OS X内部也是UNIX,也提供了(Curses库),Curses库是一个用于进行控制台画面的控制操作等的综合库,在上面只使用了其中的6个函数,下面是这些函数的概要:

    initscr生成屏幕并初始化库,使用Curses库时必须最先调用该函数
    cbreak禁止行缓冲
    noecho禁止输入的字符显示在画面上
    refresh刷新画面
    getch返回输入的字符
    endwin使用库时用于最后的收尾函数,使用Curses

    因为Curses库中没有提供putch函数,所以在上面采用的是标准库的putchar函数来显示一个字节。Curses库有单独的输出机制,因此规格和C语言标准库的printf函数和putchar函数等兼容性不强,大家尤其需要注意以下两点:

    1. 换行符的操作不同:即便使用printf函数和putchar函数输出换行符'\n',光标也只会移动到下一行,而不是移动到下一行的开头。想要把光标移动到下一行的开头,就需要输出换行符\n和回车符\r,所以在上面的代码的输出中输出了\n\r。
    2. 即使输出换行符也无法清除缓存:一般来说,输出换行符后,堆积在缓冲区中未输出的字符就会显示在画面上,然而使用Curses库时不然。因此在上面的代码中为了确保能正常输出就调用了fflush函数。

    通用头文件

    在两个不同环境中使用程序,实现的方法也大相径庭。因为分环境来编写程序非常麻烦,所以我们来生成一个能吸收两个环境的差异的库。作为头文件来实现的话,只要包含头文件就可以使用,非常方便,我们看下面这段代码:

    1. #ifndef __GETPUTCH
    2. #define __GETPUTCH
    3. #if defined(_MSC_VER)||(__TURBOC__)||(LSI_C)
    4. #include
    5. static void init_getputch(void){}
    6. static void term_getputch(void) {}
    7. #else
    8. #include
    9. #undef putchar
    10. #undef puts
    11. #undef printf
    12. static char __buf[4096]
    13. static int __putchar(int ch)//相当于putchar函数(用“换行符+回车符”代替换行符进行输出)
    14. {
    15. if (ch == '\n')
    16. putchar('\r');
    17. return putchar(ch);
    18. }
    19. static int putch(int ch)显示一个字符,清除缓存区
    20. {
    21. int result = putchar(ch);
    22. fflush(stdout);
    23. return result;
    24. }
    25. static int __printf(const char *format,...)//相当于printf函数(用“换行符+回车符”代替换行符进行输出)
    26. {
    27. va_list ap;
    28. int count;
    29. va_start(ap, format);
    30. vsprintf(__buf, format, ap);
    31. va_end(ap);
    32. for (count = 0; __buf[count]; count++)
    33. {
    34. putchar(__buf[count]);
    35. if (buf[count] == '\n')
    36. putchar('\r');
    37. }
    38. return count;
    39. }
    40. static int __puts(const char* s)//相当于puts函数(用“换行符+回车符”代替换行符进行输出)
    41. {
    42. int i, j;
    43. for (i = 0, j = 0; s[i]; i++)
    44. {
    45. __buf[j++] = s[i];
    46. if (s[i] == '\n')
    47. __buf[j++] = '\r';
    48. }
    49. return puts(__buf);
    50. }
    51. static void init_getputch(void)//库初始化处理
    52. {
    53. initscr();
    54. cbreak();
    55. noecho();
    56. refresh();
    57. }
    58. static void term_getputch(void)//库终止处理
    59. {
    60. endwin();
    61. }
    62. #define putchar __putchar
    63. #define printf __printf
    64. #define puts __puts
    65. #endif // defined(_MSC_VER)||(__TURBOC__)||(LSI_C)
    66. #endif // !__GETPUTCH

    包含头文件保护的头文件的设计

    头文件“getputch.h”包括函数(不只是声明)的定义。如果多次包含这种包括函数定义的头文件,就会因重复定义函数而发生编译错误。大家可能会想:哪有人会把同一个头文件包含两三次啊。但实际却不像大家想的那样。例如,假设头文件“abc.h”中包含了“curses.h”。这样一来下面这种情况下,"curses.h"就会被包含两次。

    1. #include "curses.h"
    2. #include "abc.h"

    因此,为了让头文件“getputch.h”无论被包含多少次都不会令程序发生故障,我们使用了一个被称为头文件保护的方法,该方法通常表示为如下形式:

    1. #ifndef __HEADERXX
    2. #define __HEADERXX
    3. /* 声明和定义等 */
    4. #endif

    此处所示的头文件第一次被包含时,宏__HEADERXX处于未定义状态,因此计算机会读取被#ifndef和#endif括起来的部分,并定义宏__HEADERXX。但是,自第二次被包含起,由于宏__HEADERXX已经被定义了,因此计算机会跳过这部分。此外,宏的名称__HEADERXX必须对应不同头文件来分别设定,在上面我们就将"getputch.h"头文件的宏名称设成了__GETPUTCH。头文件“getputch.h”的机制为:在判断编程环境为Windows类还是UNIX类后再去切换应采用的范围。

    改良后的程序:

    1. #include
    2. #include
    3. #include
    4. #include
    5. #include"getputch.h"
    6. #define MAX_STAGE 10
    7. #define swap(type,x,y) do{type t=x;x=y;y=t;}while(0)
    8. int main()
    9. {
    10. int i, j, x, stage;
    11. int dgt[9] = { 1,2,3,4,5,6,7,8,9 };
    12. int a[10];
    13. double jikan;
    14. clock_t start, end;
    15. init_getputch();
    16. srand(time(NULL));
    17. printf("请输入重复的数字。\n");
    18. printf("按下空格键开始。\n");
    19. fflush(stdout);
    20. while (getch() != ' ')
    21. ;
    22. start = clock();
    23. for (stage = 0; stage < MAX_STAGE; stage++)
    24. {
    25. int x = rand() % 9;
    26. int no;
    27. i = j = 0;
    28. while (i < 9)
    29. {
    30. a[j++] = dgt[i];
    31. if (i == x)
    32. a[j++] = dgt[i];
    33. i++;
    34. }
    35. for (i = 9; i > 0; i--)
    36. {
    37. int j = rand() % (i + 1);
    38. if (i != j)
    39. swap(int, a[i], a[j]);
    40. }
    41. for (i = 0; i < 10; i++)
    42. printf("%d ", a[i]);
    43. printf(":");
    44. fflush(stdout);
    45. do
    46. {
    47. no = getch();
    48. if (isprint(no))
    49. {
    50. putch(no);
    51. if (no != dgt[x] + '0')
    52. putch('\b');
    53. else
    54. printf("\n");
    55. fflush(stdout);
    56. }
    57. } while (no != dgt[x] + '0');
    58. }
    59. end = clock();
    60. jikan = (double)(end-start) / CLOCKS_PER_SEC;
    61. printf("用时%.1f秒。\n", jikan);
    62. if (jikan > 25.0)
    63. printf("反应太慢了。\n");
    64. else if (jikan > 15.0)
    65. printf("还可以。\n");
    66. else
    67. printf("反应神速。\n");
    68. return 0;
    69. }

    我们调试一下看看效果:

    跟之前的程序不同,在这个程序中,只要不按下空格程序就不会进行开始,用于实现这一操作的是while语句,只要getch函数返回的字符不是空白字符,这个while语句就会持续循环,这个while语句控制的循环体“;”是仅由分号构成的空语句。在do...while语句负责处理输入的字符时,这部分和改良前使用scanf函数不同,较为复杂。首先,getch函数读取从键盘输入的值,并把该值赋值给no,这个过程中输入的字符不会显示在画面上,下面的if...else语句部分只会在已读取的字符为显示字符时运行,此处会进行如下操作:

    1. 首先,通过putch函数来显示已读取的字符no。
    2. 其次,根据对错分别执行不同的处理。

    当字符no不是正确答案时: 

    输出退格符'\b',把光标位置往前退一格。这项处理是为了让接下来输入的字符能够再次显示在同一个位置上。

    当字符no是正确答案时

    输出换行符"\n",这是为了进入到下一个问题而做的准备。

  • 相关阅读:
    黄素单核苷酸小麦麦清白蛋白纳米粒|石杉碱甲乳清白蛋白纳米粒Huperzine-whey protein|化学试剂
    Karmada 1.3 新特性:基于污点的故障迁移
    大厂sql真题讲解(黑马)
    软考-防火墙技术与原理
    Bra12同态加密方案初步学习
    练[HarekazeCTF2019]encode_and_encode
    Sigrity仿真之POWER DC操作步骤
    Spring面试百题集
    (附源码)springboot码头作业管理系统 毕业设计 341654
    【Axure教程】雷达扫描动态效果(航空信息可视化案例)
  • 原文地址:https://blog.csdn.net/zjh20040819/article/details/138104707