嵌入式学习之路——上(c语言、数据结构)

一、 C语言(5天)

第一天:7月4日

学习内容:

  • C 简介、C 环境设置、C 程序结构、C 基本语法、C 数据类型、C 变量、C 常量、C 存储类、C 运算符。

学习心得:

1、C 基础结构

首先,程序开始,需要引用基础的头文件方便直接执行某些基础封装函数,使用"#include <头文件>"这句话用于引用各类函数所需的头文件,如printf(),便是"stdio.h"内的函数,用于打印字符的指令;想要运行代码,必须得写上"main()函数"又称为主函数,用于运行代码与函数(格式如:"数据类型  main(){ 代码;  return 0; }");return 0为主函数的返回值。

2、C 的简介

C是什么语言?C起源于什么?诞生于什么时候?为什么被这么广泛的使用?

答:①C是一种高级编程语言;②起源于UNiX操作系统的编程;③诞生自1970年,1978年被公开;④因为C兼容各种文本编译器,支持多线程代码运行,操作方便,逻辑清晰;

3、C 的环境设置:

C需要什么环境才能编辑?如何安装环境?

答:①需要“文本编辑器”或“C编辑器”;②文本编辑器无需安装,现在的电脑自带,只要创个.txt的文件,编写完毕后改为.c即可,但不能编译你写的代码。C编辑器可以编译(指将代码转换为机器语言,根据内置指令便可执行代码功能),最常用的C编辑器为“GNU 的 C/C++ 编译器”,安装方法如下:

UNIX/Linux 上的安装

如果使用的是 Linux 或 UNIX,请在命令行使用下面的命令来检查您的系统上是否安装了 GCC:

$ gcc -v

如果您的计算机上已经安装了 GNU 编译器,则会显示如下消息:

Using built-in specs. Target: i386-redhat-linux Configured with: ../configure --prefix=/usr ....... Thread model: posix gcc version 4.1.2 20080704 (Red Hat 4.1.2-46)

如果未安装 GCC,那么请按照 Installing GCC- GNU Project 上的详细说明安装 GCC。

Visual studio code的安装

1、安装“嵌入式环境”(“visual studio code 1.59.0”);

2、安装插件,主要插件为“C/C++(intelliSense...)和Chiness(中文)”;

3、安装附件“MinGW”,才能运行.c文件。

4、在终端内输入:gcc .\hello.c -o hello,创建.exe运行文件;

5、再输入:.\hello,运行.exe文件,结果会显示在终端内,如下图所示;

4、C 语言程序结构

5、C 基本语法

什么是基本语法?有哪些需要注意点地方?

答:①一个编程语言需要遵守的基本格式;②“;”,分号代表每一段代码的结束,如果没带会报错;“//”和“/**/”,一行注释多行注释,在程序运行时会忽略该符号后的内容;“_”标志符,用于给变量取名时更也辨识度更直观(随便一提,C是区分大小写的语言,变量A与a是不同的变量);关键字,这些字符不能作为变量,有特殊定义与作用(如if else啥的);空行,运行时也会直接忽略,为了程序更加美观而生。

6、C 数据类型

什么是数据类型?有哪些基本的数据类型?数据类型有多少种?

答:①用于定义变量类型的“关键字”;②基本的数据有:整型(int)、符号型(char)、浮点型(float)、双精度浮点型(duoble);③有四种,分别是“基本类型”、“枚举类型”、“void类型”、“派生类型(如数组,指针和结构体类)”。

在实例中若想知道数据类型的大小,可以使用“sizeof(数据类型)”用printf打印出来查看,如下:

7、C 变量

什么是变量?用来干嘛的?

答:①被用作储存可变数据的字符(可自定义,不过要遵守语法规范)称为变量;②用来进行运算或是更好控制某一部分逻辑的数值(总之有很多用法);变量可以不用定义初值,有默认值;变量定义后可使用“extern声明变量

8、C 常量

什么是常量?如何定义?

答:①就是一个固定的数值,类型有整、浮点、字符、字符串;②一般用#define(声明任意变量)const(声明只读变量)。

9、C 存储类型

什么是存储类型?要怎么使用?有哪些存储类型?

答:①用于定义变量的使用域,生命周期和存储位置;②写在修饰类型的前面如“auto int 变量”;③有“auto类型(是所有局部变量的默认类型)”、“register类型(用于定义在寄存器中的变量而不是RAM中的局部变量,不能用&运算法,因为他没有内存位)”、“static类型(被它修饰的变量无论是局部还是全局都不会被重置数值)”、“extern类型(用于在其他文件中声明变量或函数)”

10、C 运算符

运算符有哪些类型?分别是哪些?

答:“算术运算符(+ - * / % ++ --)”、“关系运算符(== >= <= != > <)”、“逻辑运算符(|| && !)”、“位运算符(| & ^ << >>)”、“赋值运算符(算数符+=)”。


第二天:7月5日

学习内容:

  • C 判断、C 循环、C 函数、C 作用域规则、C 数组、C enum(枚举)。

学习心得:

1、C 判断

默认定义有哪些?判断在C语言中有哪些形式?

答:①在判断条件中0或null为假,1或满足条件的情况为真。②判断以“if-else-else if串行条件”)、“switch并行条件判断)”和“?:”这三种形式;还可使用嵌套使其逻辑更加精密。

2、C 循环

循环指令有哪些?

答:循环指令分为“循环类型(while,for,do...while)”与“循环控住指令(break,gotocontinue)”;解释:while是大循环,可以容纳很多东西,也可以做无限循环使用格式为“while(1)”,for为小循环,涵盖内容较少,用于遍历数组之类的数据,do while用法与while相同但他会先执行内部功能,再进行while的判断,break为终止循环或 switch,continue为跳出目前所处的循环,goto是直接跳转运行位置。

3、C 函数

函数是什么?

答:函数就是用于存放某个功能代码或计算方式的“打包盒”,打包在一个函数里的代码可以通过调用自定义函数名的方法,来调用这个函数的内容;函数也可以声明和定义类型,若定义了类型,就要返回相应的数据,如return 0.

4、C 作用域规则

作用域是什么?分别有哪些作用域?

答:①作用域就是函数的作用区域,一般都是针对变量;②有“全局变量”、“局部变量”与“形式参数”;解释:全局变量通常定义在函数外部,所有函数都可以使用的变量;局部变量通常定义在某个函数或块中,只在该函数或块中使用;形式参数被当作该函数内的局部变量,如果与全局变量同名它们会优先使用。

5、C 数组

什么是数组?

答:数组就是类似于多个相同类型数据整合在一起的“组合变量”,它是连续的,调用第一位数据,方式如data[0],data为数组名,[ ]中的数字代表调用的第几位,从0位开始。数组也可用sizeof查看尺寸大小。

6、C enum(枚举)

什么是枚举?

答:枚举是一种类似于数组的“组合变量”,但它无法像数组一样用“data[0]”形式调用,它是不连续的。枚举类型还需要定义类型和变量,初值默认从 0 开始递增,如下:


第三天:7月6日

学习内容:

  • C 指针、C 函数指针与回调函数、C 字符串、C 结构体、C 共用体、C 位域、C typedef、C 输入 & 输出。

学习心得:

1、C 指针

什么是指针?如何使用?指针还有不少内容如?

答:①指针就是内存地址指针变量就是存放内存地址的变量(用&即可访问变量的内存地址);

②使用包括“定义”、“赋值”、“访问”;用 *+指针名 的形式即定义指针(注:若要给指针赋值,那么该变量需要同指针一样的类型,顺便一提null指针地址为0x0),用&即可为指针赋值,在指针前端再加一个*即可访问指针存储的变量值。

③如:指针的算术运算指针数组指向指针的指针传递指针给函数从函数返回指针

2、C 函数指针与回调函数

函数指针是什么?回调函数是什么?

答:①用于指向函数的指针(大指针),如下,函数指针变量 p,指向函数 max,函数指针变量可以作为某个函数的参数来使用的;

 ②回调函数就是一个通过函数指针调用的函数。

3、C 字符串

c语言的字符串是什么格式的?字符串有哪些操作?

答:①是使用空字符 \0 结尾的一维字符数组,使用“char”定义;

②有“复制strcpy”,“连接strcat”,“返回长度strlen”,“判断strcmp”,“指向第一次出现的位置strchr”。如下表:

1strcpy(s1, s2);
复制字符串 s2 到字符串 s1。
2strcat(s1, s2);
连接字符串 s2 到字符串 s1 的末尾。
3strlen(s1);
返回字符串 s1 的长度。
4strcmp(s1, s2);
如果 s1 和 s2 是相同的,则返回 0;如果 s1<s2 则返回小于 0;如果 s1>s2 则返回大于 0。
5strchr(s1, ch);
返回一个指针,指向字符串 s1 中字符 ch 的第一次出现的位置。
6strstr(s1, s2);
返回一个指针,指向字符串 s1 中字符串 s2 的第一次出现的位置。
4、C 结构体

什么是结构体?怎么定义?如何“访问”结构体的“成员”?

答:①一个由多个不同类型数据组成的数组(但不连续);②使用struct 定义,如下所示,有好几种方式:

 ③使用成员访问运算符(.),如下:

5、C 共用体

什么是共用体?怎么定义?如何“访问”成员?

答:①在相同的内存位置,存储不同的数据类型的一种数据定义结构;(注:共用体所有成员共用一个空间,所有任何时候都要注意在合适时机赋值);②用union定义,类似结构体,但与结构体不同的是它每个成员都不是独立的,结构体每个成员都是独立的且有自己的独立空间;定义如下:

 ③使用成员访问运算符(.),如下:

 但要注意,成员共用同一个内存,所有上述代码,运行后会出现这样的结果:(所以使用共用体时需要注意成员赋值的时机)

6、C 位域

什么是位域?如何定义?需要注意什么?

答:①位域是一种特殊的结构体,它可以自定义成员的数据长度(也就是“二进制位数”);②定义如下

 ③需要注意赋值不能超过自定义的位数,如下:

7、C typedef

这个关键字有什么作用?

答:①可以使用它来为类型取一个新的名字(它还有个“老表”叫“define”,不仅可以为类型定义别名,也能为数值定义别名),如下:

8、C 输入 & 输出

c语言通常使用什么做输入输出?还有哪些用于输入输出的函数呢?

答:①C 语言中的 输入输出 通常使用 printf() scanf() 两个函数;②getchar()输入一个字符串 , putchar()是输出一个字符;gets() 输入字符串, puts()输出字符串;


第四天:7月7日

学习内容:

  • C 文件读写、C 预处理器、C 头文件、C 强制类型转换、C 错误处理、C 递归、C 可变参数、C 内存管理 、C 命令行参数。

学习心得:

1、C 文件读写

文件读写分别有哪些操作?

答:(1)打开文件: fopen( ) (注:所有关于文件的操作都基于FILE类型)

(2)关闭文件fclose( ) 

(3)写入文件fputc( )、fputs( )或fprintf( )

(4)读取文件fgetc( )、fgets( )或fscanf( )

2、C 预处理器

预处理器是什么?有哪些操作?

答:①是对数据预处理指令的一个统称,又称CPP,下列为重要预处理指令:

②有(1)预定义的宏

(2)预处理器运算符:宏换行(\)、字符串常量化(#)、标记粘贴(##)

(3)参数化的宏: #define

3、C 头文件

什么是头文件?头文件有哪些操作呢?

答:①头文件就是开头#include< >内的文件(注:也可以用" "引用头文件),头文件通常用.h,里面包含了诸多函数的宏定义与声明,有了头文件我们才能使用一些基础的指令。(头文件也可以我们自行编写.c为主内容,.h为头文件);

②有(1)正常引用式头文件:

(2)只引用一次式头文件:

(3)条件引用式头文件

4、C 强制类型转换

怎么进行强制类型转换?

答:可以用“ ( 数据类型 ) ”来强行改变数据类型;(注:c语言中在计算时会自动将其他类型的数据转换为同一种数据类型以得到最终结果)

5、C 错误处理

错误怎么查看?(改就直接查着改咯)

答:①在c语言中无论什么错误,都会用“errno”这个变量来形容(注:但在程序退出状态时,errno无变化,可以通过EXIT_SUCCESS和EXIT_FAILURE(-1为有错)判断是否出现错误);我们可以使用“ perror() 和 strerror() ”进行查看,如下:

6、C 递归

什么是递归?有哪些操作可以用上递归?

答:①是一种在定义的函数内使用这个函数的操作(我用我自己);

 ②(1)数的阶乘:

(2)斐波那契数列:

7、C 可变参数

什么是可变参数?

答:就是参数数量是可变的的函数,格式如下:

8、C 内存管理

如何进行内存管理?有哪些操作呢?

答:①使用几个函数分别是“*calloc( )”、“free( )”、“*malloc( )”、“*realloc( )”,具体功能如下:

 9、C 命令行参数

什么是命令行的参数?怎么看?

答:①执行程序时,从命令行传给 C 的值,这些值被称为命令行参数;命令行参数是使用 main() 函数参数来处理的,其中,argc 是指传入参数的个数,argv[] 是一个指针数组,指向传递给程序的每个参数,如下:


第五天:7月8日-7月11日

学习内容:

  • 复习第一天到第四天所有知识点
  • 挑选10道C语言练手题目,尝试自己编写

学习心得:

1、题目一:有 1、2、3、4 四个数字,能组成多少个互不相同且无重复数字的三位数?都是多少?

分析:核心思路“无重复数字的三位数”与嵌套运行顺序的使用;

实际成果:虽然我经过分析知道有几个三位数,但我却无法用程序语言进行表达

2、题目二:企业发放的奖金根据利润提成。

  • 利润(I)低于或等于10万元时,奖金可提10%;
  • 利润高于10万元,低于20万元时,低于10万元的部分按10%提成,高于10万元的部分,可提成7.5%;
  • 20万到40万之间时,高于20万元的部分,可提成5%;
  • 40万到60万之间时高于40万元的部分,可提成3%;
  • 60万到100万之间时,高于60万元的部分,可提成1.5%;
  • 高于100万元时,超过100万元的部分按1%提成。

从键盘输入当月利润I,求应发放奖金总数?

分析:按照利润与奖金的关系写出判断语句,注意计算法语。

实际成果:

3、题目三:一个整数,它加上100后是一个完全平方数,再加上168又是一个完全平方数,请问该数是多少?

分析:通过函数的对应关系反推这个这个整数为多少。

实际成果:

4、题目四:输入某年某月某日,判断这一天是这一年的第几天?

分析:搞清每月前的日子,再判断是否为闰年,为闰年加一即可。

实际成果:

5、题目五:输入三个整数x,y,z,请把这三个数由小到大输出。

分析:按逻辑写判断条件即可。

实际成果:左为思路一,右为思路二

6、题目六:用*号输出字母C的图案。

分析:直接printf最简单。

实际成果:

7、题目七:古典问题(兔子生崽):有一对兔子,从出生后第3个月起每个月都生一对兔子,小兔子长到第三个月后每个月又生一对兔子,假如兔子都不死,问每个月的兔子总数为多少?(输出前40个月即可)

分析:循环次数为月数,因为每月兔子的数量才会变动,直接叠加数量即可。

实际成果:

8、题目八:输出9*9口诀。

分析:适当运用二层嵌套即可。

实际成果:

9、题目九:判断 101 到 200 之间的素数。

分析:这题的关键在于判断素数的方法,用一个数分别去除 2 到 sqrt(这个数),如果能被整除,则表明此数不是素数,反之是素数。

实际成果:

10、题目十:打印出所有的"水仙花数"。所谓"水仙花数"是指一个三位数,其各位数字立方和等于该数 本身。例如:153是一个"水仙花数",因为153=1的三次方+5的三次方+3的三次方。

分析:只要在100与999之中穷举,会拆分百位十位个位进行操作即可。

实际成果:


二、 数据结构(7天)

第一天:7月12日

学习内容:

  • 数据结构的基本内容。

学习心得:

1、数据结构的基本内容

什么是数据结构,为什么数据结构总伴随着算法?有哪些相关的基本概念和常用术语?

答:(1)数据结构是一种数字存取的结构方式,其目的是为了更好更高效达成算法(重点在于数据的“插入”“寻找”);

①实例一:实现一个函数PrintN,使得传入一个正整数N后,能顺序打印从1-N的数。

解释分析:此题可使用两种标志性的算法“循环”和“递归”,但这两种方法有着本质上的区别;

 结果解读:一旦数据量变大,“递归”的空间占比会远远高于“循环”,用时也就高于“循环”,故要想提升效率也需要考虑空间(有效)利用率。

②实例一:写程序,计算给定多项式在给定点x处的值。

代码如下:(注:因为是在探究数据结构对代码的影响也就是效率,所以在这里加上clock,用于计算函数运行一次所需要的时间)

//例2:计算给定多项式在给定点x处的值
#include <stdio.h>
#include <math.h>
#include <time.h>
#define MAXK 1e7 //被测函数最大重复调用次数
clock_t start, stop; //定义计时变量
double duration;
//方法一:被鄙视的算法
double f1(int n, double a[], double x)
{
    int i;
    double p=a[0];
    for(i=1;i<=n;i++)
        p+=(a[i]*pow(x,i));
    return p;
}
//方法二:优化后的算法
double f2(int n, double a[], double x)
{
    int i;
    double p=a[n];
    for(i=n;i>0;i--)
        p+=a[i-1]+x*p;
    return p;
}
int main()
{
    int i;
    double a[10];
    for(i=0;i<10;i++)a[i]=(double)i;

    start = clock();//开始计时
    for(i=0;i<MAXK;i++) //加入循环以待程序能够被时钟检测到(因为太快了)
        f1(10-1,a,1.1);   //待测函数
    stop = clock(); //停止计时
    duration = ((double)(stop - start))/CLK_TCK/MAXK;//通过平均值求得每一次运行的时间
    printf("ticksl=%f\n",(double)((stop - start)));
    printf("duration=%6.2e\n",duration);

    start = clock();//开始计时
    for(i=0;i<MAXK;i++)
        f2(10-1,a,1.1);   //待测函数
    stop = clock(); //停止计时
    duration = ((double)(stop - start))/CLK_TCK/MAXK;
    printf("ticksl=%f\n",(double)((stop - start)));
    printf("duration=%6.2e\n",duration);

    return 0;
}

结果解读:可以观察测到,f1运行一次所用的时间远远大于f2,故算法的效率还与程序的巧妙程度有关。

(2)数据类型:数据对象集(变量),数据集合相关联的操作集(函数)。抽象数据类型(一种描述数据类型的方法不看具体实现的类型):与存放数据的机器无关,与数据存储的物理结构无关,与实现操作的算法、语言无关,(说人话就是,只描述数据对象集和相关操作集“是什么”,而不谈“怎么做到的”)。


第二天:7月13日

学习内容:

  • 线性结构:线性表及其实现、堆栈、队列

学习心得:

前言:分析数据结构,主要从“数据对象集”和“操作集”分析,类型名是次要的。
1、线性表及其实现
(1)什么是线性表?

类型名称】线性表(List);

数据对象集】线性表就是由n(>=0)个元素组成的有序序列(a1,a2,...,an);(解释)

操作集】线性表L属于List,用i表示位置,X属于ElementType;

  基本操作如下:

(2)线性表如何存储?

①方法一:顺序存储

        存储实现:

         主要操作:(初始化、查找、插入、删除)

②方法二:链式存储

特点:删除、插入不需要对移动数据元素,只需要修改链。

        存储实现:

         主要操作:(求表长、查找、插入、删除)

2、堆栈
引言概念:
  • 中缀表达式运算符号位于两数运算数之间,如,a*b+c/d-e。

  • 后缀表达式运算符号位于两数运算数之后,如,abc*+de/-。由此引出了“堆栈”的概念。

  • 中转后缀:

(1)什么是堆栈?

类型名称】堆栈(Stack);

数据对象集】一个有0个或多个元素的有穷线性表,具备后入先出的特点;(解释)

操作集】长度为MaxSize的堆栈,S∈Stack,item∈ElementType;

  基本操作如下:(删除为“出栈(Push)”插入为“入栈(Pop)”,且只在一端做插入删除

(2)堆栈如何存储?

①方法一:顺序存储

        存储实现:

        主要操作:((入栈(插入)、出栈(删除)))

           单堆栈:

            双堆栈:

方法二:链式存储

        存储实现:

         主要操作:(入栈(插入)、出栈(删除))

 3、队列
(1)什么是队列?

类型名称】队列(Queue);

数据对象集】一个有0个或多个元素的有穷线性表,具备先进先出的特点;(解释)

操作集】长度为MaxSize的队列,Q∈Queue,队列元素item∈ElementType;

  基本操作如下:(删除为“入队列(AddQ)”插入为“出队列(DeleteQ)”两端都可以做插入删除

(2)队列如何存储?

①方法一:顺序存储

        存储实现:

        主要操作:(插入(出队列)、删除(入队列))

 ②方法二:链式存储

        存储实现:

        主要操作:(插入(出队列)、删除(入队列))


第三天:7月14日

学习内容:

  • 树 :树与树的表示、二叉树及存储结构、二叉树的遍历

学习心得:

前言:客观世界中存在许多层次关系,如社会族谱等。那么此时最重要也是最基本的操作之一就是——“查找(Searching)”。那么如何有效率的进行查找呢?

        在c语言中,“查找”操作是根据某个给定值的关键字K,从集合R中找出K的相关记录的一种操作,它分为“静态查找”和“动态查找”。静态查找中,集合的记录是固定的,只能查找;动态查找中,集合的记录是会发生变化的,可能发生插入与删除操作

  静态查找:(二分查找效率更高)

1、树与树的表示
(1)什么是树?

        树(Tree) :n (n>=0)个结点构成的有限集合。当n=0时,称为空树;

(2)树与非树?

(3)树的基本属于有哪些?

 (4)在程序如何表示树呢?

2、二叉树
(1)什么是二叉树?

类型名称】二叉树;

数据对象集】一个有穷的结点集合。这个集合可以为空,若不为空,则由根结点与称为其左子树TL右子树TR的两个不相交的二叉树组成。;(解释)

操作集】BT∈BinTree,item∈ElementType;

        基本操作如下:

(2)二叉树有哪些形态?

(3)二叉树有什么重要性质?

(4)二叉树的存储结构有哪些?

3、二叉树的遍历

(1)递归式遍历(使用数组)
        ①先序遍历:

        ②中序遍历:

        ③后序遍历:

(2)非递归式遍历(使用堆栈)


第四天:7月15日

学习内容:

  • 排序(上):简单排序(冒泡、插入)、希尔排序、堆排序、归并排序

学习心得:

前言:没有任何一种排序是在任何情况下表现最好的,故具体问题具体分析。

1、简单排序冒泡插入
(1)什么是冒泡?

        答:从第一个元素开始,前一个元素与后一个元素相比较,后一个小于前一个则交换

(2)什么是插入?

        答:从第二个元素开始,下一张牌只要比入手的牌大就切换

引入新概念:时间复杂度下界(意思就是排序效率)

2、希尔排序
(1)什么是希尔排序?

        如下例:(满足交换间隔较远,每次可消除不止一个逆序列)

(2)如何避免增量元素不互质?

        答:直接定义互质的形式规律的增量序列即可,如下:

 结论:算法虽然简单,但时间复杂度非常难分析。

3、堆排序
引言:选择排序(如下)

 (1)如何实现堆排序?

 结论:虽然堆排序给出最佳平均时间复杂度,但实际效果不如用Sedgewick增量序列的希尔排序。

4、归并排序
(1)归并排序怎么实现?( 核心:两个有序子序列的归并)

(2)归并排序的“递归算法”如何实现?
        核心代码 :

          统一接口:

(3)归并排序的“非递归算法”如何实现? 
         核心思路与代码 :

          统一接口:


第五天:7月16日

学习内容:

  • 排序(下):快速排序、表排序、基数排序、排序算法的比较

学习心得:

前言:
1、快速排序
(1)怎样实现快速排序?

        思路代码:快速排序可以在绝大多数情况快速实现排序,但需要掌握写法,不然适得其反

 (2)如何编写好快速排序呢?

        从这几点出发:“选主元”、“子集划分”、“”

        ①选主元:

         ②子集划分:

                快速排序的子集划分会面临一个问题“如果有元素正好等于pivot怎么办?——停下来交换

         ③小规模数据的处理:

 (3)实际代码如何实现快速排序?
        代码实现:

        接口: 

2、表排序
(1)如何实现表排序?

 (2)若是非要动“书(元素)”呢?用什么方法?
        物理排序:

         时间复杂度:

3、基数排序

前言:前面的排序都是基于“比较大小”的排序,他们总会遇到一种情况就算使用最快的算法也只能跑到“N logN”(时间复杂度),那有没用一种方法可以更快呢?

(1)技术排序的元祖——桶排序?(但面临一个问题,当M比N要大很多,就无法用这种算法处理了,会需要创建很多“桶”,所以就要后来的“基数排序”)

(2)如何实现基数排序?

 (3)实际案例“多关键字的基数排序”
        方法一:不是很聪明的方法

        方法二:次位优先 

4、排序算法的比较

总结:各种排序算法的属性与优点


第六天:7月17日

学习内容:

  • 自选项目一实现:学生管理系统

学习心得:

学生管理系统

        要求:首先进入“用户登录”界面最开始有1-2个默认存在的用户,输入用户名和密码之后即可进入“学生管理系统”进行6种操作,分别是“查看学生”、“查找学生”、“增加学生”、“修改学生”、“删除学生”以及“退出系统”。具体要求如下:

  1. 登录与注册:用户可以通过输入用户名和密码进行登录,如果没有账号,可以选择注册一个新账号。

  2. 学生信息管理:用户可以查看学生列表、添加学生、修改学生信息和删除学生。

  3. 文件存储:学生的信息会以CSV格式存储在本地文件中,包括学号、姓名、性别、年龄、班级和分数等信息。

  4. 排序功能:用户可以选择按照学号、年龄或分数对学生列表进行升序或降序排序。

  5. 查找功能:用户可以通过输入学号、姓名或班级关键字来查找学生信息。

  6. 密码隐藏:在输入密码时,会以星号(*)的形式隐藏实际输入的字符。

  7. 用户权限管理:用户需要输入正确的授权码才能注册新账号。

  8. 学生链表:使用链表数据结构来存储学生信息,方便插入、删除和遍历操作。

  9. 界面友好:使用字符界面来展示学生列表和菜单选项,方便用户操作。

(1)首先建立好“登录与注册”环节
        代码如下:
#include <stdio.h>
#include <stdbool.h>
#include <string.h>

int mod=1, name=0;

bool isUserFile(const char* username, const char* password, const char* filename)
{
    FILE* file = fopen(filename, "r");
    if(file == NULL){
        printf("无法打开文件 %s\n",filename);
        return false;
    }        
    
    char line[256];
    while(fgets(line, sizeof(line), file)!=NULL){
        char storedUsername[50];
        char storedPassword[50];
        if(sscanf(line, "%[^,],%s", storedUsername, storedPassword)==2){
            if(strcmp(username, storedUsername)==0 && strcmp(password, storedPassword)==0){
                fclose(file);
                return true;
            }
        }
    }
    fclose(file);
    return false;
}
void signIn()
{
    //获取用户名和密码输入‘
    char username[50];
    char password[50];   
    printf("用户名:");
    scanf("%s", username);
    printf("密码:");
    scanf("%s", password);

    if(isUserFile(username, password, "users.csv")){
        printf("登陆成功!\n");
        mod=2;
    }
    else{
        printf("用户名或密码错误!");
    }
}


int main()
{
    
    printf("=========学生管理系统==========\n");
    printf("=    欢迎进入该系统,请登录   =\n");
    printf("===============================\n");
    while(1)
    {
        switch (mod)
        {
        case 1:
            signIn();
            break;
        
        default:
            break;
        }

    }
    return 0;
}
        效果图:        

(2)学生信息管理部分
    ①查看功能

        代码:

/*展示功能*/
void show() {
    puts("1.1 学号升序\t 1.2 学号降序");
    puts("2.1 年龄升序\t 2.2 年龄降序");
    puts("3.1 分数升序\t 3.2 分数降序");
    printf("默认以学号升序排列,请选择:");
    char option[4];
    fflush(stdin);
    sort(gets(option));
    puts("\n======================= 学生表 =========================\n");
    puts("- - - - - - - - - - - - - - - - - - - - - - - - - - - - -");
    puts("|学号\t| 姓名\t| 性别\t| 年龄\t| 班级\t\t| 分数\t|");
    puts("- - - - - - - - - - - - - - - - - - - - - - - - - - - - -");

    // 遍历输出
    for (Node *pTemp = students->pNext; pTemp != NULL; pTemp = pTemp->pNext) {
        printf("|%s\t| %s\t| %s\t| %d\t| %s\t| %.1lf\t|\n", pTemp->student.id, pTemp->student.name,
               pTemp->student.gender, pTemp->student.age, pTemp->student.class, pTemp->student.score);
        puts("- - - - - - - - - - - - - - - - - - - - - - - - - - - - -");
    }
}

        效果图:

    ②查找功能

        代码:

/*查找功能*/
void find() {
    printf("请输入关键字(学号、姓名、班级):");
    char keyword[20];
    fflush(stdin);
    gets(keyword);
    puts("\n======================= 学生表 =========================\n");
    puts("- - - - - - - - - - - - - - - - - - - - - - - - - - - - -");
    puts("|学号\t| 姓名\t| 性别\t| 年龄\t| 班级\t\t| 分数\t|");
    puts("- - - - - - - - - - - - - - - - - - - - - - - - - - - - -");
    for (Node *pTemp = students->pNext; pTemp != NULL; pTemp = pTemp->pNext) {
        if (strcmp(keyword, pTemp->student.id) == 0 || strcmp(keyword, pTemp->student.name) == 0 || strstr(pTemp->student.class, keyword) != NULL) {
            printf("|%s\t| %s\t| %s\t| %d\t| %s\t| %.1lf\t|\n", pTemp->student.id, pTemp->student.name,
                   pTemp->student.gender, pTemp->student.age, pTemp->student.class, pTemp->student.score);
            puts("- - - - - - - - - - - - - - - - - - - - - - - - - - - - -");
        }
    }
}

        效果图:

    ③添加功能

        代码:

/*添加功能*/
void add() {
    puts("- - - - - - - - - - - - - - - - - - - - - - - - - -");
    puts("学号\t姓名\t性别\t年龄\t班级\t\t分数");
    puts("- - - - - - - - - - - - - - - - - - - - - - - - - -");
    char info[256];
    while (true) {
        fflush(stdin);
        gets(info);     // 获取学生信息
        if (strcmp(info, "ok") == 0) {      // 若输入ok则完成添加
            break;
        } else {
            Node *pNode = (Node *) malloc(sizeof(Node));
            Student student = getStudentByInfo(info, "\t");
            bool isNotExist = true;
            // 遍历链表,查询是否学生已经存在
            for (Node *pTemp = students->pNext; pTemp != NULL; pTemp = pTemp->pNext) {
                if (strcmp(student.id, pTemp->student.id) == 0) {
                    isNotExist = false;
                    printf("已存在%s号学生,已跳过该生添加\n", student.id);
                }
            }
            if (isNotExist) {
                pNode->student = student;
                writerStudent(student);
                pTail->pNext = pNode;
                pTail = pTail->pNext;
                pTail->pNext = NULL;
            }
        }
    }
}

        效果图:

    ④删除功能

        代码:

/*删除功能*/
void delete() {
    Node *pNode = findNode('p');
    puts("按回车确认删除,其它键取消:");
    if (getch() == '\r') {
        Node *pTemp = pNode->pNext;
        pNode->pNext = pTemp->pNext;
        free(pTemp);
        writerStudents();
        printf("删除成功!");
    } else {
        printf("已取消,");
    }
}

        效果图:

        ⑤修改功能

        代码:

/*修改功能*/
void update() {
    Node *pNode = findNode('c');
    puts("请输入新的学生信息:");
    fflush(stdin);
    char info[256];
    gets(info);
    pNode->student = getStudentByInfo(info, "\t");
    writerStudents();
    printf("修改完成!");
}

        效果图:

(3) 总体代码
#include <stdio.h>
#include <stdbool.h>    // bool、true、false所在头文件
#include <string.h>
#include <stdlib.h>     // system、atoi、itoa函数所在头文件
#include <conio.h>      // getch函数所在头文件

#define AUTHORIZECODE "ProgrammerIsSoNiuBi"     // 授权码

/*学生结构体*/
typedef struct student {
    char id[5];
    char name[30];
    char gender[4];
    int age;
    char class[20];
    double score;
} Student;

/*链表节点结构体*/
typedef struct node {
    Student student;
    struct node *pNext;
} Node;

/*一堆函数声明*/
void signIn(char username[], char password[]);
void signUp(char username[], char password[]);
void getPassword(char password[]);
Node *createLinkedList();
void show();
void sort(char option[]);
void add();
void find();
Student getStudentByInfo(char *info, char *delimiter);
void writerStudent(Student student);
void update();
void writerStudents();
void delete();
Node *findNode(char which);

bool signInStatus = false;  // 登录成功标记,默认为未登录
Node *students;             // 学生链表头指针
Node *pTail;                // 学生链表尾指针

int main() {
    // 1. 登录
    while (!signInStatus) {
        signIn(NULL, NULL);
    }

    // 2. 读取本地学生表到链表
    students = createLinkedList();

    // 3. 主菜单
    while (true) {
        fflush(stdin);
        system("cls");
        puts(" ┏━━━━━━━━━━━━━━━━━━━━━━━┓ ");
        puts(" ┃      学生管理系统     ┃ ");
        puts(" ┣━━━━━━━━━━━━━━━━━━━━━━━┫ ");
        puts(" ┃      1. 查看学生      ┃ ");
        puts(" ┃      2. 查找学生      ┃ ");
        puts(" ┃      3. 增加学生      ┃ ");
        puts(" ┃      4. 修改学生      ┃ ");
        puts(" ┃      5. 删除学生      ┃ ");
        puts(" ┃      6. 退出系统      ┃ ");
        puts(" ┗━━━━━━━━━━━━━━━━━━━━━━━┛");
        printf("请选择:");
        switch (getchar()) {
            case '1':
                show();
                break;
            case '2':
                find();
                break;
            case '3':
                add();
                break;
            case '4':
                update();
                break;
            case '5':
                delete();
                break;
            case '6':
                exit(0);
            default:
                printf("输入错误!");
        }
        system("pause");
    }
}

/*登录功能*/
void signIn(char *username, char *password) {
    if (username == NULL && password == NULL) {
        username = (char *) malloc(sizeof(char) * 20);
        password = (char *) malloc(sizeof(char) * 20);
        puts("=========== 登录 ============");
        printf("用户名:");
        gets(username);
        printf("密码:");
        getPassword(password);
    }
    // 打开用户列表root
    FILE *fp = fopen("./users.csv", "r");
    if (fp == NULL) {
        puts("用户文件打开失败,请检查!");
        exit(1);
    } else {
        char user[50];
        bool isNotExist = true;
        // 打开成功后读取每一行,一行则代表一个用户:username,password
        while (fgets(user, 50, fp) != NULL) {
            unsigned int tail = strlen(user) - 1;
            // 将行末的回车符删除
            if (user[tail] == '\n') {
                user[tail] = '\0';
            }
            // 截取用户名和密码,并和输入的用户名密码作比较
            if (strcmp(username, strtok(user, ",")) == 0) {
                isNotExist = false;
                if (strcmp(password, strtok(NULL, ",")) == 0) {
                    // 如果用户名和密码都正确,则登录成功
                    signInStatus = true;
                    printf("登录成功!");
                    system("pause");
                } else {
                    puts("密码错误!");
                }
            }
        }
        // 遍历完文件后若未发现用户,则提示注册
        if (isNotExist) {
            puts("查无此用户!按回车重新输入,按其他键注册");
            if (getch() != '\r') {
                signUp(username, password);
            }
        }
    }
    fclose(fp);
}

/*注册功能*/
void signUp(char *username, char *password) {
    // 打开用户列表
    FILE *fp = fopen("./users.csv", "a");
    if (fp == NULL) {
        puts("用户文件打开失败,请检查!");
        exit(1);
    } else {
        printf("请确认密码:");
        char repeat[20];
        getPassword(repeat);
        if (strcmp(password, repeat) == 0) {
            printf("请输入授权码:");
            char code[50];
            gets(code);
            // 两次密码一致且授权码正确,则写入注册用户信息
            if (strcmp(code, AUTHORIZECODE) == 0) {
                fputs(username, fp);
                putc(',', fp);
                fputs(password, fp);
                fputc('\n', fp);
                fflush(fp);
                signInStatus = true;
                printf("注册成功!已自动登录,");
                system("pause");
            } else {
                puts("授权码错误!");
            }
        } else {
            puts("两次密码不一致");
        }
    }
    fclose(fp);
}

/*创建学生链表*/
Node *createLinkedList() {
    Node *pHead = (Node *) malloc(sizeof(Node));
    pTail = pHead;

    // 打开用户列表
    FILE *fp = fopen("./students.csv", "r");
    if (fp == NULL) {
        puts("学生表文件打开失败,请检查!");
        exit(1);
    } else {
        char info[256];
        // 打开成功后读取每一行,一行则代表一个学生:id,name,gender,age,class,score
        while (fgets(info, 256, fp) != NULL) {
            unsigned int tail = strlen(info) - 1;
            // 将行末的回车符删除
            if (info[tail] == '\n') {
                info[tail] = '\0';
            }
            // 挂节点,没学链表的话就别看这段代码了
            Node *pNode = (Node *) malloc(sizeof(Node));
            pNode->student = getStudentByInfo(info, ",");
            pTail->pNext = pNode;
            pTail = pTail->pNext;
            pTail->pNext = NULL;
        }
    }
    return pHead;
}

/*展示功能*/
void show() {
    puts("1.1 学号升序\t 1.2 学号降序");
    puts("2.1 年龄升序\t 2.2 年龄降序");
    puts("3.1 分数升序\t 3.2 分数降序");
    printf("默认以学号升序排列,请选择:");
    char option[4];
    fflush(stdin);
    sort(gets(option));
    puts("\n======================= 学生表 =========================\n");
    puts("- - - - - - - - - - - - - - - - - - - - - - - - - - - - -");
    puts("|学号\t| 姓名\t| 性别\t| 年龄\t| 班级\t\t| 分数\t|");
    puts("- - - - - - - - - - - - - - - - - - - - - - - - - - - - -");

    // 遍历输出
    for (Node *pTemp = students->pNext; pTemp != NULL; pTemp = pTemp->pNext) {
        printf("|%s\t| %s\t| %s\t| %d\t| %s\t| %.1lf\t|\n", pTemp->student.id, pTemp->student.name,
               pTemp->student.gender, pTemp->student.age, pTemp->student.class, pTemp->student.score);
        puts("- - - - - - - - - - - - - - - - - - - - - - - - - - - - -");
    }
}

/*查找功能*/
void find() {
    printf("请输入关键字(学号、姓名、班级):");
    char keyword[20];
    fflush(stdin);
    gets(keyword);
    puts("\n======================= 学生表 =========================\n");
    puts("- - - - - - - - - - - - - - - - - - - - - - - - - - - - -");
    puts("|学号\t| 姓名\t| 性别\t| 年龄\t| 班级\t\t| 分数\t|");
    puts("- - - - - - - - - - - - - - - - - - - - - - - - - - - - -");
    for (Node *pTemp = students->pNext; pTemp != NULL; pTemp = pTemp->pNext) {
        if (strcmp(keyword, pTemp->student.id) == 0 || strcmp(keyword, pTemp->student.name) == 0 || strstr(pTemp->student.class, keyword) != NULL) {
            printf("|%s\t| %s\t| %s\t| %d\t| %s\t| %.1lf\t|\n", pTemp->student.id, pTemp->student.name,
                   pTemp->student.gender, pTemp->student.age, pTemp->student.class, pTemp->student.score);
            puts("- - - - - - - - - - - - - - - - - - - - - - - - - - - - -");
        }
    }
}

/*添加功能*/
void add() {
    puts("- - - - - - - - - - - - - - - - - - - - - - - - - -");
    puts("学号\t姓名\t性别\t年龄\t班级\t\t分数");
    puts("- - - - - - - - - - - - - - - - - - - - - - - - - -");
    char info[256];
    while (true) {
        fflush(stdin);
        gets(info);     // 获取学生信息
        if (strcmp(info, "ok") == 0) {      // 若输入ok则完成添加
            break;
        } else {
            Node *pNode = (Node *) malloc(sizeof(Node));
            Student student = getStudentByInfo(info, "\t");
            bool isNotExist = true;
            // 遍历链表,查询是否学生已经存在
            for (Node *pTemp = students->pNext; pTemp != NULL; pTemp = pTemp->pNext) {
                if (strcmp(student.id, pTemp->student.id) == 0) {
                    isNotExist = false;
                    printf("已存在%s号学生,已跳过该生添加\n", student.id);
                }
            }
            if (isNotExist) {
                pNode->student = student;
                writerStudent(student);
                pTail->pNext = pNode;
                pTail = pTail->pNext;
                pTail->pNext = NULL;
            }
        }
    }
}

/*删除功能*/
void delete() {
    Node *pNode = findNode('p');
    puts("按回车确认删除,其它键取消:");
    if (getch() == '\r') {
        Node *pTemp = pNode->pNext;
        pNode->pNext = pTemp->pNext;
        free(pTemp);
        writerStudents();
        printf("删除成功!");
    } else {
        printf("已取消,");
    }
}

/*修改功能*/
void update() {
    Node *pNode = findNode('c');
    puts("请输入新的学生信息:");
    fflush(stdin);
    char info[256];
    gets(info);
    pNode->student = getStudentByInfo(info, "\t");
    writerStudents();
    printf("修改完成!");
}

/*传参数'c'返回匹配的当前节点,传其它参数返回前驱节点*/
Node *findNode(char which) {
    printf("请输入学生的学号或姓名:");
    char keyword[20];
    fflush(stdin);
    gets(keyword);
    puts("- - - - - - - - - - - - - - - - - - - - - - - - - - -");
    puts("学号\t姓名\t性别\t年龄\t班级\t\t分数\t");
    puts("- - - - - - - - - - - - - - - - - - - - - - - - - - -");
    Node *pTemp;
    for (pTemp = students->pNext; pTemp != NULL; pTemp = pTemp->pNext) {
        if (strcmp(keyword, pTemp->pNext->student.id) == 0 || strcmp(keyword, pTemp->pNext->student.name) == 0) {
            printf("%s\t%s\t%s\t%d\t%s\t%.1lf\t\n", pTemp->pNext->student.id, pTemp->pNext->student.name,
                   pTemp->pNext->student.gender, pTemp->pNext->student.age, pTemp->pNext->student.class,
                   pTemp->pNext->student.score);
            puts("- - - - - - - - - - - - - - - - - - - - - - - - - - -");
            break;
        }
    }
    if (which == 'c') {
        return pTemp->pNext;
    }
    return pTemp;
}

/*把整个链表写回本地*/
void writerStudents() {
    fopen("./students.csv", "w");
    for (Node *pTemp = students->pNext; pTemp != NULL; pTemp = pTemp->pNext) {
        writerStudent(pTemp->student);
    }
}

/*追加写一个学生到本地*/
void writerStudent(Student student) {
    FILE *fp = fopen("./students.csv", "a");
    if (fp == NULL) {
        puts("学生表文件打开失败,请检查!");
        exit(1);
    } else {
        fputs(student.id, fp);
        fputc(',', fp);
        fputs(student.name, fp);
        fputc(',', fp);
        fputs(student.gender, fp);
        fputc(',', fp);
        char buffer[10];
        fputs(itoa(student.age, buffer, 10), fp);
        fputc(',', fp);
        fputs(student.class, fp);
        fputc(',', fp);
        fputs(gcvt(student.score, 5, buffer), fp);
        fputc('\n', fp);
        fflush(fp);
    }
    fclose(fp);
}

/*对学生链表进行排序*/
void sort(char option[]) {
    int opt = atoi(strtok(option, "."));
    for (Node *pStart = students->pNext; pStart != NULL; pStart = pStart->pNext) {
        Node *pMin = pStart;
        for (Node *pTemp = pStart->pNext; pTemp != NULL; pTemp = pTemp->pNext) {
            switch (opt) {
                default:
                    if (strcmp(pTemp->student.id, pMin->student.id) < 0) {
                        pMin = pTemp;
                    }
                    break;
                case 2:
                    if (pTemp->student.age < pMin->student.age) {
                        pMin = pTemp;
                    }
                    break;
                case 3:
                    if (pTemp->student.score < pMin->student.score) {
                        pMin = pTemp;
                    }
                    break;
            }
        }
        Student student = pMin->student;
        pMin->student = pStart->student;
        pStart->student = student;
    }

    // 如果需要降序,则反转链表
    if (atoi(strtok(NULL, ".")) == 2) {
        Node *pTemp = students->pNext;
        Node *pNew = NULL;
        students->pNext = NULL;

        while (pTemp != NULL) {
            //读取新一个节点后,之前的新节点后移
            pNew = pTemp;
            pTemp = pTemp->pNext;
            pNew->pNext = students->pNext;    //先把头节点后面的第一个节点挂到新节点后面
            students->pNext = pNew;           //再把新节点挂到头节点后面
        }
    }
}

/*隐式地输入密码*/
void getPassword(char password[]) {
    int i;
    for (i = 0; (password[i] = (char) getch()) != '\r'; ++i) {
        putchar('*');
    }
    password[i] = '\0';
    printf("\n");
}

/*根据学生信息和信息分隔符返回学生变量*/
Student getStudentByInfo(char *info, char *delimiter) {
    Student student;
    strcpy(student.id, strtok(info, delimiter));
    strcpy(student.name, strtok(NULL, delimiter));
    strcpy(student.gender, strtok(NULL, delimiter));
    student.age = atoi(strtok(NULL, delimiter));
    strcpy(student.class, strtok(NULL, delimiter));
    student.score = atof(strtok(NULL, delimiter));
    return student;
}

第七天:7月18日

学习内容:

  • 自选项目二实现:贪食蛇

学习心得:

贪食蛇

     要求:贪吃蛇的主要逻辑包括以下几个方面:

  1. 游戏初始化:绘制游戏界面,包括地图边框、初始食物和蛇身。

  2. 蛇的移动:根据用户按键操作,控制蛇头的移动方向。蛇身的每一节也会跟随蛇头移动,形成连续的移动效果。每次移动,需要擦除蛇尾的位置,以实现蛇身的移动效果。

  3. 食物生成和吃食物:在地图上随机生成食物,并判断蛇头是否碰到食物。如果蛇头碰到食物,蛇的长度增加一节,得分增加,并重新生成食物。

  4. 碰撞检测:判断蛇头是否碰到了边界或自身的身体。如果蛇头碰到边界或与自身的身体相撞,游戏结束。

  5. 游戏结束:输出游戏结束信息,包括得分,并等待一段时间后程序结束。

(1)游戏初始化
//******************绘制游戏边框+随机生成初试食物+蛇的初始化(属性+头+身子)****************//
void drawMap()
{
    //*********绘制游戏边框***********
    //打印上下边框
    for(i=0; i<=MAPWIDTH; i+=2)
    {
        gotoxy(i, 0);//移动光标,打印上边框
        printf("■");

        gotoxy(i, MAPHEIGHT);//打印下边框
        printf("■");
    }
    //打印左右边框
    for(i=1; i<MAPHEIGHT; i++)
    {
        gotoxy(0, i);//打印左边框
        printf("■");

        gotoxy(MAPWIDTH,i );//打印右边框
        printf("■");
    }

    //*********随机生成初试食物***********
    while (1)
    {
        srand((unsigned int)time(NULL));
        food.x = rand() % (MAPWIDTH - 4)+2;
        food.y = rand() % (MAPHEIGHT -2)+1;
        //生成的食物横坐标的奇偶必须和初试时蛇头所在坐标的奇偶一致,因为一个字符占两个字节位置,若不一致
		//会导致吃食物的时候只吃到一半
        if(food.x %2 == 0) break;       
    }
    //将光标移到食物的坐标处,然后打印食物
    gotoxy(food.x, food.y);
    printf("@");

    //*********初始化蛇***********
    //初始化蛇的属性
    snake.len = 3;
    snake.speed = 250;
    //在屏幕正中生成:蛇头
    snake.x[0] = MAPWIDTH /2 +1;
    snake.y[0] = MAPHEIGHT /2;
    //打印蛇头
    gotoxy(snake.x[0], snake.y[0]);
    printf("■");
    //生成初试的蛇身
    for(i=1; i<snake.len; i++)
    {
        snake.x[i] = snake.x[i-1] +2;//蛇身的打印,纵坐标不变,横坐标为上一节蛇身的坐标值+2
        snake.y[i] = snake.y[i-1];
        gotoxy(snake.x[i], snake.y[i]);
        printf("■");
    }
    gotoxy(MAPWIDTH -2, 0);
}
(2)蛇的移动/按键操作
//**************************************按键操作***************************************//
void keyDown()
{
    int pre_key = key; //记录前一个按键的方向
    if(_kbhit())//判断,如果用户按下了键盘中的某个键
    {
        fflush(stdin);//先清空缓冲区字符
        //getch()读取方向键的时候,会返回两次,第一次调用返回0或者224,第二次调用返回的才是实际值
		key = _getch();//第一次调用返回的不是实际值
		key = _getch();//第二次调用返回实际值
    }

    /*蛇移动时候先擦去蛇尾的一节
	*changeFlag=0表明此时没有吃到食物,因此每走一步就要擦除掉蛇尾,以此营造一个移动的效果
	*为1表明吃到了食物,就不需要擦除蛇尾,以此营造一个蛇身增长的效果
	*/
    if(changeFlag == 0)
    {
        gotoxy(snake.x[snake.len - 1], snake.y[snake.len - 1]);//光标定位蛇尾
        printf("  ");//擦去蛇尾(2个符号)
    }

    //将蛇的每一节依次向前移动一节(蛇头除外)
    for(i =snake.len - 1; i>0; i--)
    {
        snake.x[i] = snake.x[i -1];
        snake.y[i] = snake.y[i -1];
    }

    //蛇当前移动的方向不能和前一次的方向相反,比如蛇往左走的时候不能直接按右键往右走
	//如果当前移动方向和前一次方向相反的话,把当前移动的方向改为前一次的方向
    if(pre_key == 72 && key == 80) key = 72;
    if(pre_key == 80 && key == 72) key = 80;
    if(pre_key == 75 && key == 77) key = 75;

    /*控制台按键所代表的数字
	*“↑”:72
	*“↓”:80
	*“←”:75
	*“→”:77
	*/
    //判断蛇头向哪个方向移动
    switch (key)
    {
    case 75:
        snake.x[0] -= 2;//往左
        break;
    case 77:
        snake.x[0] += 2;//往右
        break;
    case 72:
        snake.y[0]--;//往上
        break;
    case 80:
        snake.y[0]++;//往下
        break;    
    }

    //打印出蛇头
    gotoxy(snake.x[0], snake.y[0]);
    printf("■");
    gotoxy(MAPWIDTH - 2, 0);
    changeFlag = 0;//由于目前没有吃到食物,changFlag值为0
}
(3)食物生成和吃食物
//************************************随机生成食物**************************************//
void createFood()
{
    if(snake.x[0] == food.x && snake.y[0] == food.y)//蛇头碰到食物
    {
        while(1)//蛇头碰到食物即为要吃掉这个食物了,因此需要再次生成一个食物
        {
            int flag = 1;
            srand((unsigned int)time(NULL));
            food.x = rand() % (MAPWIDTH - 4) + 2;
            food.y = rand() % (MAPHEIGHT -2) + 1;
            //随机生成的食物不能在蛇的身体上
            for(i=0; i<snake.len; i++)
            {
                if(snake.x[i] == food.x && snake.y[i] == food.y)
                {
                    flag = 0;
                    break;
                }
            }
            //随机生成的食物不能横坐标为奇数,也不能在蛇身,否则重新生成
            if(flag && food.x % 2 == 0) break;           
        }
        //绘制食物
        gotoxy(food.x, food.y);
        printf("@");

		snake.len++;//吃到食物,蛇身长度加1
		score += 10;//每个食物得10分
		snake.speed -= 5;//随着吃的食物越来越多,速度会越来越快
		changeFlag = 1;//很重要,因为吃到了食物,就不用再擦除蛇尾的那一节,以此来造成蛇身体增长的效果
    }
}
(4)碰撞检测
//************************************蛇的状态判断**************************************//
bool snakeStatus()
{
    //蛇头碰到上下边界,游戏结束
    if(snake.y[0] == 0 || snake.y[0] == MAPHEIGHT) return false;
    //蛇头碰到左右边界,游戏结束
    if(snake.x[0] == 0 || snake.x[0] == MAPWIDTH) return false;
    //蛇头碰到蛇身,游戏结束
    for(i=1; i<snake.len; i++)
    {
        if(snake.x[i] == snake.x[0] && snake.y[i] == snake.y[0]) return false;
    }
    return true;
}
(5)游戏结束
    //游戏结束"界面"
    gotoxy(MAPWIDTH / 2, MAPHEIGHT / 2);
    printf("哦豁,Game Over了๑乛◡乛๑\n");
    gotoxy(MAPWIDTH / 2, MAPHEIGHT / 2 + 1);
    printf("本次游戏只得了:%d\n",score);
    Sleep(5000);
    return 0;
(6)总体代码
/*
 * @Author: error: error: git config user.name & please set dead value or install git && error: git config user.email & please set dead value or install git & please set dead value or install git
 * @Date: 2023-07-18 10:33:05
 * @LastEditors: error: error: git config user.name & please set dead value or install git && error: git config user.email & please set dead value or install git & please set dead value or install git
 * @LastEditTime: 2023-07-18 15:21:38
 * @FilePath: \Shuju_day\day7-18-2.c
 * @Description: 这是默认设置,请设置`customMade`, 打开koroFileHeader查看配置 进行设置: https://github.com/OBKoro1/koro1FileHeader/wiki/%E9%85%8D%E7%BD%AE
 */
#include <stdio.h>
#include <stdlib.h>
#include <conio.h>
#include <time.h>
#include <Windows.h>//windows编程头文件

#define SNAKESIZE 100//蛇的身体最大节数
#define MAPWIDTH 78//宽度
#define MAPHEIGHT 24//高度

#ifndef __cplusplus

typedef char bool; //能够让bool函数被定义
#define false 0 
#define true  1

#endif

//食物的坐标
struct {
    int x;
    int y;
}food;

//蛇的相关属性
struct {
	int speed;//蛇移动的速度
	int len;//蛇的长度
	int x[SNAKESIZE];//组成蛇身的每一个小方块中x的坐标
	int y[SNAKESIZE];//组成蛇身的每一个小方块中y的坐标
}snake;

int key = 72;//表示蛇移动的方向,72为按下“↑”所代表的数字
int changeFlag = 0;//用来判断蛇是否吃掉了食物,这一步很重要,涉及到是否会有蛇身移动的效果以及蛇身增长的效果
int score = 0;//记录玩家的得分
int i;

//游戏初始化
void drawMap();
//随机生成食物
void createFood();
//按键操作
void keyDown();
//出局判定
bool snakeStatus();
//从控制台移动光标
void gotoxy(int x, int y);

//*************************主函数************************//
int main() 
{
    drawMap();//游戏初始化
    while(1)
    {
        keyDown();//按键操作
        if(!snakeStatus()) break;//出局判定
        createFood();//食物生成
        Sleep(snake.speed);
    }
    //游戏结束"界面"
    gotoxy(MAPWIDTH / 2, MAPHEIGHT / 2);
    printf("哦豁,Game Over了๑乛◡乛๑\n");
    gotoxy(MAPWIDTH / 2, MAPHEIGHT / 2 + 1);
    printf("本次游戏只得了:%d\n",score);
    Sleep(5000);
    return 0;
}

//*************************将光标移动到控制台的(x,y)坐标点处*****************************//
void gotoxy(int x,int y)
{
    COORD coord;//COORD是 Windows API 中定义的一个结构体,用于表示具有 X 和 Y 值的坐标点,这里就改个名
    coord.X = x;
    coord.Y = y;
    //↓ Windows API 函数,用于设置控制台光标的位置
    SetConsoleCursorPosition(GetStdHandle(STD_OUTPUT_HANDLE), coord);
}

//******************绘制游戏边框+随机生成初试食物+蛇的初始化(属性+头+身子)****************//
void drawMap()
{
    //*********绘制游戏边框***********
    //打印上下边框
    for(i=0; i<=MAPWIDTH; i+=2)
    {
        gotoxy(i, 0);//移动光标,打印上边框
        printf("■");

        gotoxy(i, MAPHEIGHT);//打印下边框
        printf("■");
    }
    //打印左右边框
    for(i=1; i<MAPHEIGHT; i++)
    {
        gotoxy(0, i);//打印左边框
        printf("■");

        gotoxy(MAPWIDTH,i );//打印右边框
        printf("■");
    }

    //*********随机生成初试食物***********
    while (1)
    {
        srand((unsigned int)time(NULL));
        food.x = rand() % (MAPWIDTH - 4)+2;
        food.y = rand() % (MAPHEIGHT -2)+1;
        //生成的食物横坐标的奇偶必须和初试时蛇头所在坐标的奇偶一致,因为一个字符占两个字节位置,若不一致
		//会导致吃食物的时候只吃到一半
        if(food.x %2 == 0) break;       
    }
    //将光标移到食物的坐标处,然后打印食物
    gotoxy(food.x, food.y);
    printf("@");

    //*********初始化蛇***********
    //初始化蛇的属性
    snake.len = 3;
    snake.speed = 250;
    //在屏幕正中生成:蛇头
    snake.x[0] = MAPWIDTH /2 +1;
    snake.y[0] = MAPHEIGHT /2;
    //打印蛇头
    gotoxy(snake.x[0], snake.y[0]);
    printf("■");
    //生成初试的蛇身
    for(i=1; i<snake.len; i++)
    {
        snake.x[i] = snake.x[i-1] +2;//蛇身的打印,纵坐标不变,横坐标为上一节蛇身的坐标值+2
        snake.y[i] = snake.y[i-1];
        gotoxy(snake.x[i], snake.y[i]);
        printf("■");
    }
    gotoxy(MAPWIDTH -2, 0);
}

//**************************************按键操作***************************************//
void keyDown()
{
    int pre_key = key; //记录前一个按键的方向
    if(_kbhit())//判断,如果用户按下了键盘中的某个键
    {
        fflush(stdin);//先清空缓冲区字符
        //getch()读取方向键的时候,会返回两次,第一次调用返回0或者224,第二次调用返回的才是实际值
		key = _getch();//第一次调用返回的不是实际值
		key = _getch();//第二次调用返回实际值
    }

    /*蛇移动时候先擦去蛇尾的一节
	*changeFlag=0表明此时没有吃到食物,因此每走一步就要擦除掉蛇尾,以此营造一个移动的效果
	*为1表明吃到了食物,就不需要擦除蛇尾,以此营造一个蛇身增长的效果
	*/
    if(changeFlag == 0)
    {
        gotoxy(snake.x[snake.len - 1], snake.y[snake.len - 1]);//光标定位蛇尾
        printf("  ");//擦去蛇尾(2个符号)
    }

    //将蛇的每一节依次向前移动一节(蛇头除外)
    for(i =snake.len - 1; i>0; i--)
    {
        snake.x[i] = snake.x[i -1];
        snake.y[i] = snake.y[i -1];
    }

    //蛇当前移动的方向不能和前一次的方向相反,比如蛇往左走的时候不能直接按右键往右走
	//如果当前移动方向和前一次方向相反的话,把当前移动的方向改为前一次的方向
    if(pre_key == 72 && key == 80) key = 72;
    if(pre_key == 80 && key == 72) key = 80;
    if(pre_key == 75 && key == 77) key = 75;

    /*控制台按键所代表的数字
	*“↑”:72
	*“↓”:80
	*“←”:75
	*“→”:77
	*/
    //判断蛇头向哪个方向移动
    switch (key)
    {
    case 75:
        snake.x[0] -= 2;//往左
        break;
    case 77:
        snake.x[0] += 2;//往右
        break;
    case 72:
        snake.y[0]--;//往上
        break;
    case 80:
        snake.y[0]++;//往下
        break;    
    }

    //打印出蛇头
    gotoxy(snake.x[0], snake.y[0]);
    printf("■");
    gotoxy(MAPWIDTH - 2, 0);
    changeFlag = 0;//由于目前没有吃到食物,changFlag值为0
}

//************************************随机生成食物**************************************//
void createFood()
{
    if(snake.x[0] == food.x && snake.y[0] == food.y)//蛇头碰到食物
    {
        while(1)//蛇头碰到食物即为要吃掉这个食物了,因此需要再次生成一个食物
        {
            int flag = 1;
            srand((unsigned int)time(NULL));
            food.x = rand() % (MAPWIDTH - 4) + 2;
            food.y = rand() % (MAPHEIGHT -2) + 1;
            //随机生成的食物不能在蛇的身体上
            for(i=0; i<snake.len; i++)
            {
                if(snake.x[i] == food.x && snake.y[i] == food.y)
                {
                    flag = 0;
                    break;
                }
            }
            //随机生成的食物不能横坐标为奇数,也不能在蛇身,否则重新生成
            if(flag && food.x % 2 == 0) break;           
        }
        //绘制食物
        gotoxy(food.x, food.y);
        printf("@");

		snake.len++;//吃到食物,蛇身长度加1
		score += 10;//每个食物得10分
		snake.speed -= 5;//随着吃的食物越来越多,速度会越来越快
		changeFlag = 1;//很重要,因为吃到了食物,就不用再擦除蛇尾的那一节,以此来造成蛇身体增长的效果
    }
}

//************************************蛇的状态判断**************************************//
bool snakeStatus()
{
    //蛇头碰到上下边界,游戏结束
    if(snake.y[0] == 0 || snake.y[0] == MAPHEIGHT) return false;
    //蛇头碰到左右边界,游戏结束
    if(snake.x[0] == 0 || snake.x[0] == MAPWIDTH) return false;
    //蛇头碰到蛇身,游戏结束
    for(i=1; i<snake.len; i++)
    {
        if(snake.x[i] == snake.x[0] && snake.y[i] == snake.y[0]) return false;
    }
    return true;
}



下篇笔记:嵌入式学习之路——中(计算机组成原理)

  • 2
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值