# C
关于C语言的重点难点学习笔记!
# 用C实现推箱子小游戏
# 推箱子的核心要素
地图,可以用二维数组描述,不同的变量代表地图中不同的含义
int map_1[10][10] = { { 0,0,1,1,1,0,0,0 }, { 0,0,1,4,1,0,0,0 }, { 0,0,1,0,1,1,1,1 }, { 1,1,1,0,0,2,4,1 }, { 1,4,2,2,0,1,1,1 }, { 1,1,1,3,2,1,0,0 }, { 0,0,1,1,4,1,0,0 }, { 0,0,0,1,1,1,0,0 } };
可以移动的小人:在移动的过程中,地图元素会被改变,因此需要不断的刷新地图
# 核心代码
推箱子的核心代码如下
#include<stdio.h>
#include<stdlib.h>
#include<string.h>
#include<windows.h>
#include<conio.h>
int x = 0, y = 0;
//存储当前使用的地图
int map[10][10] = { 0 };
//地图 1
int map_1[10][10] = {
{ 0,0,1,1,1,0,0,0 },
{ 0,0,1,4,1,0,0,0 },
{ 0,0,1,0,1,1,1,1 },
{ 1,1,1,0,0,2,4,1 },
{ 1,4,2,2,0,1,1,1 },
{ 1,1,1,3,2,1,0,0 },
{ 0,0,1,1,4,1,0,0 },
{ 0,0,0,1,1,1,0,0 }
};
//地图 2
int map_2[10][10] = {
{ 0,0,0,1,1,1,1,1,1,1 },
{ 0,0,1,1,0,0,1,0,3,1 },
{ 0,0,1,0,0,0,1,0,0,1 },
{ 0,0,1,2,0,2,0,2,0,1 },
{ 0,0,1,0,2,1,1,0,0,1 },
{ 1,1,1,0,2,0,1,0,1,1 },
{ 1,4,4,4,4,4,0,0,1,0 },
{ 1,1,1,1,1,1,1,1,1,0 },
};
//地图 3
int map_3[10][10] = {
{ 1,1,1,1,1,1,1,1 },
{ 1,0,0,0,1,0,0,1 },
{ 1,0,1,0,1,2,4,1 },
{ 1,0,0,0,0,2,4,1 },
{ 1,0,1,0,1,2,4,1 },
{ 1,0,0,0,1,0,0,1 },
{ 1,1,1,1,1,3,0,1 },
{ 0,0,0,0,1,1,1,1 },
};
//地图 4
int map_4[10][10] = {
{ 0,1,1,1,1,1,0,0 },
{ 0,1,0,3,0,1,1,1 },
{ 1,1,0,1,2,0,0,1 },
{ 1,0,5,4,0,4,0,1 },
{ 1,0,0,2,2,0,1,1 },
{ 1,1,1,0,1,4,1,0 },
{ 0,0,1,0,0,0,1,0 },
{ 0,0,1,1,1,1,1,0 },
};
//判断游戏是否挑战成功
int finish();
//控制小人移动
void move(int x1, int y1, int x2, int y2);
//根据二维数组,渲染地图
int render();
//查找小人所在的当前位置
void find();
//根据用户输入,确定用户选择哪张地图
void setmap(int n);
//设置文本显示时的颜
void color(int m);
int main() {
int n;
char dir;
char c;
printf( "*******************推箱子********************\n"
"* *\n"
"* ----------------------- *\n"
"* | Powered By sutinghu game! | *\n"
"* ----------------------- *\n"
"* *\n"
"* ♀:自己的位置 *\n"
"* ☆:箱子要到的的位置 *\n"
"* ■:箱子的位置 *\n"
"* *\n"
"* | 游戏规则 | *\n"
"* *\n"
"* 控制♀将■推到☆的位置 *\n"
"* 按 wasd 控制♀的移动 *\n"
"* 按 r 重新开始游戏 *\n"
"* 按 q 退出游戏 *\n"
"*********************************************\n");
color(0xA);
printf("请选择关卡:(1/2/3/4)(0:退出游戏):");
while (scanf("%d", &n)) {
getchar();
//n==0,游戏退出
if (n == 0) {
printf("Game Over\n");
break;
}
system("cls");//清空命令行窗口中显示的所有信息
if (n > 0) {
setmap(n);//根据 n 的值,设置当前地图
render();//根据所选地图,在命令行窗口中将其渲染出来
//游戏开始,用户输入字符w/a/s/d,控制小人移动
while ((dir = getch()) != EOF) {
system("cls");//移动之前,删除原有命令行中的地图
find();//移动之前,需找到当前小人所在的位置
//根据 dir 字符的值,控制小人的移动,以及设置重新开始和退出选项的操作
switch (dir) {
case 'w':move(x - 1, y, x - 2, y); break;
case 's':move(x + 1, y, x + 2, y); break;
case 'a':move(x, y - 1, x, y - 2); break;
case 'd':move(x, y + 1, x, y + 2); break;
case 'r':setmap(n); break;
case 'q':return 0;
}
//重新渲染人移动后的场景
render();
//每次移动,判断是否挑战成功
if (finish()) {
printf("游戏胜利,请重新选择关卡(1/2/3/4)(0:退出游戏):");
break;
}
}
}
else {
printf("输入有误,请重新输入\n");
printf("请选择关卡:(1/2/3/4)(0:退出游戏):");
}
}
system("pause");
return 0;
}
// 按w的时候的输出结果
void move(int x1, int y1, int x2, int y2) {
if (map[x][y] == 3) //找到自己的位置
{
//人前边是箱子,箱子在空格上
if (map[x1][y1] == 2) {
//箱子前边是空格
if (map[x2][y2] == 0) {
map[x][y] = 0;
map[x1][y1] = 3;
map[x2][y2] = 2;
}
//箱子前边是位置
if (map[x2][y2] == 4) {
map[x][y] = 0;
map[x1][y1] = 3;
map[x2][y2] = 5;
}
}
//人前是箱子,箱子在位置上
if (map[x1][y1] == 5) {
//箱子前边是空格
if (map[x2][y2] == 0) {
map[x][y] = 0;
map[x1][y1] = 6;
map[x2][y2] = 2;
}
//箱子前边是位置
if (map[x2][y2] == 4) {
map[x][y] = 0;
map[x1][y1] = 6;
map[x2][y2] = 5;
}
}
//人前为空格
if (map[x1][y1] == 0) {
map[x1][y1] = 3;
map[x][y] = 0;
}
if (map[x1][y1] == 4) {
map[x][y] = 0;
map[x1][y1] = 6;
}
}
if (map[x][y] == 6) //人在位置上
{
//位置前是箱子,箱子在空格上
if (map[x1][y1] == 2) {
//箱子前是空格
if (map[x2][y2] == 0) {
map[x][y] = 4;
map[x1][y1] = 3;
map[x2][y2] = 2;
}
//箱子前是位置
if (map[x2][y2] == 4) {
map[x][y] = 4;
map[x1][y1] = 3;
map[x2][y2] = 5;
}
}
//位置前是箱子,箱子在位置上
if (map[x1][y1] == 5) {
//箱子前是空格
if (map[x2][y2] == 0) {
map[x][y] = 4;
map[x1][y1] = 6;
map[x2][y2] = 2;
}
//箱子前是位置
if (map[x2][y2] == 4) {
map[x][y] = 4;
map[x1][y1] = 6;
map[x2][y2] = 5;
}
}
//人前面是位置
if (map[x1][y1] == 4) {
map[x][y] = 4;
map[x1][y1] = 6;
}
//人前面是空格
if (map[x1][y1] == 0) {
map[x][y] = 4;
map[x1][y1] = 3;
}
}
}
void find() {
for (x = 0; x < 10; x++) {
for (y = 0; y < 10; y++) {
if (map[x][y] == 3 || map[x][y] == 6) {
return;
}
}
}
}
int render() {
for (x = 0; x<10; x++) {
for (y = 0; y<10; y++) {
if (map[x][y] == 1) {
color(8);
printf("■"); //输出砖块的样子
}
if (map[x][y] == 3) {
color(3);
printf("♀"); //输出自己的位置
}
if (map[x][y] == 2) {
color(4);
printf("■"); //输出箱子
}
if (map[x][y] == 4) {
color(0xE);
printf("☆"); //输出箱子要到的位置
}
if (map[x][y] == 0) {
color(0xF);
printf(" "); //输出空白
}
if (map[x][y] == 5) {
color(6);
printf("★"); //箱子到达目标位置后的图标
}
if (map[x][y] == 6) {
color(3);
printf("♀");//人达到星星处,要由☆改为♀
}
}
printf("\n");
}
return 0;
}
void setmap(int n) {
if (n == 1) {
memcpy(map, map_1, sizeof(map_1));
}
if (n == 2) {
memcpy(map, map_2, sizeof(map_2));
}
if (n == 3) {
memcpy(map, map_3, sizeof(map_3));
}
if (n == 4) {
memcpy(map, map_4, sizeof(map_4));
}
}
int finish()
{
for (x = 0; x < 10; x++) {
for (y = 0; y < 10; y++) {
if (map[x][y] == 2)
return 0;
}
}
return 1;
}
void color(int m) {
HANDLE consolehend;
consolehend = GetStdHandle(STD_OUTPUT_HANDLE);
SetConsoleTextAttribute(consolehend, m);
}
# 游戏结果
# C 指针问题
# 什么是指针?
程序中数据被保存在内存中,每个内存都具有专属的地址,这个地址就是指针,为了保存一个数据在内存中的地址,就需要专门用来保存内存地址的变量,这种变量被称为指针变量。
# 为什么需要指针?
- 指针是内存地址的抽象概念,内存地址本身计算机不可避免
- 指针可以用来传递数据源,以节省空间和时间
- 可以使得不同区域的代码能够轻易的共享内存数据
- C语言中链表、二叉树等结构体需要用指针来构建
- 某些必须使用到指针的操作,如申请的堆内存
# 指针类型有哪些?
c语言的指针类型跟普通类型一样,包括int、float、double、数组等
只是定义的时候需要加 * 号。
# 如何理解C语言中的* ?
- int p 定义的普通的int类型的整型变量
- p中存放的是某个具体的数值
- int *p定义的一个int * 类型的整型指针变量,p指向一个普通整型的地址
- p中存放的是某个地址
- 该地址指向的内存存放的是某个具体的数值
- int **p ,可以认为是指向一个指针变量的地址,该指针变量指向某个地址
- p中存放的是指针变量的地址
- 指针变量的地址映射的内存存放的是某个地址
- 某个地址中存放的具体的数值
- int ***p 指向某个指针变量地址的变量的地址
- p中存放的是存放指针变量的地址的指针的地址
# C中&符号的作用
指针赋值
- 当使用&时,将a的地址赋给了指针变量b
#include<stdio.h> int main() { int a = 2; int*b;//定义一个整形指针 b = &a;//给指针赋值,使指针指向a的地址 printf("%d", b);//输出的是a的地址 printf("\n");//换行符 printf("%d", *b);//*的作用是解引用,取出指针指向地址的内容,达到简 return 0; }
二目运算符中的按位与
- 单目是只需要一个操作数的意思 比如
a++ a-- *a &a
- 双目是需要两个操作数的意思 比如
a+b a-b a*b a/b a%b
- 三目是需要三个操作数的意思 比如
a=c>b?c:b
- 单目是只需要一个操作数的意思 比如
当出现&&时用于逻辑与
# C 中函数传递的几种方式
- 值传递
- 变量的值传递给函数的形式参数,实际就是用变量的值来新生成一个形式参数
- 形参是实参的拷贝,改变形参的值并不会影响外部实参的值。从被调用函数的角度来说,值传递是单向的(实参->形参),参数的值只能传入,不能传出。
- 当函数内部需要修改参数,并且不希望这个改变影响调用者时,采用值传递。
- 函数里对形参的改变不会影响到函数外的变量的值
- 地址传递
- 把变量的地址赋给函数里形式参数的指针,使指针指向真实的变量的地址
- 对指针所指地址的内容的改变能反映到函数外,能改变函数外的变量的值
- 形参为指向实参地址的指针,当对形参的指向操作时,就相当于对实参本身进行的操作
- 引用传递
- 通过指针来实现的,能达到使用的效果如传址,可是使用方式如传值。
- 形参相当于是实参的“别名”,对形参的操作其实就是对实参的操作。
- 在引用传递过程中,被调函数的形式参数虽然也作为局部变量在栈中开辟了内存空间,但是这时存放的是由主调函数放进来的实参变量的地址。
- 被调函数对形参的任何操作都被处理成间接寻址,即通过栈中存放的地址访问主调函数中的实参变量。
- 被调函数对形参做的任何操作都影响了主调函数中的实参变量。
如果传值的话,会生成新的对象,花费时间和空间,而在退出函数的时候,又会销毁该对象,花费时间和空间。
因而如果int,char等固有类型,而是你自己定义的类或结构等,都建议传指针或引用,因为他们不会创建新的对象。
#include<stdio.h>
void change(int*a, int&b, int c)
{
c=*a;
b=30;
*a=20;
}
int main ( )
{
int a=10, b=20, c=30;
change(&a,b,c);
printf(“%d,%d,%d,”,a,b,c);
return 0;
}
结果:20 30 30
解析:
1,指针传参 -> 将变量的地址直接传入函数,函数中可以对其值进行修改。
2,引用传参 -> 将变量的引用传入函数,效果和指针相同,同样函数中可以对其值进行修改。
3,值传参 -> 在传参过程中,首先将c的值复制给函数c变量,然后在函数中修改的即是函数的c变量,然后函数返回时,系统自动释放变量c。而对main函数的c没有影响。
# C语言中的sizeof操作符
# sizeof的特征
- sizeof是C语言的一种单目操作符,如C语言的其他操作符++、--等
- sizeof不是函数
- 该操作符以字节形式给出了操作数的存储大小
- 操作数可以是一个表达式或括号内的类型
- sizeof6
- sizeof(int)
- 操作数的存储大小由操作数的类型决定
# sizeof 的使用方法
# 用于数据类型
sizeof使用形式: sizeof(type)
数据类型必须用括号括住: sizeof(int)
# 用于变量
sizeof使用形式: sizeof(var_name) 或 sizeof var_name
# 用于数组
char a[5];
sizeof(a) = 5;
当操作数具有数组类型时,其结果是数组的总字节数。
# sizeof的结果
- sizeof操作符的结果类型是size_t
- 它在头文件中定义为: typedef unsigned int size_t;
- 该类型保证能容纳实现所建立的最大对象的字节大小.
sizeof(char) = 1;
sizeof(unsigned char) = 1;
sizeof(signed char) = 1;
sizeof(int) = 4;
sizeof(unsigned int) = 4;
sizeof(short int) = 2;
sizeof(unsigned short) = 2;
sizeof(long int) = 4;
sizeof(unsigned long) = 4;
sizeof(float) = 4;
sizeof(double) = 8;
sizeof(long double) = 12;
# sizeof的用途
1、主要用途是与存储分配和I/O系统那样的例程进行通信。
void *malloc(size_t size);
size_t fread(void *ptr, size_t size, size_t nmemb, FILE * stream);
2、另一个的主要用途是计算数组中元素的个数。
void *memset(void *s, int c, sizeof(s));
# malloc和free
# 动态内存分配
在C语言中,数组的定义如下
int arr[1000] = { 0 };
如果在C语言中想动态的分配内存就要定义一个常量,否则程序在编译过程中就失败。
但是,如果数组定义的大小要在程序执行的过程中,动态分配内存大小,那么在程序在不同环境下执行的时候,如何确定每次分配的内存大小呢?
int* arr = (int*)malloc(sizeof(int) * N)
可以采用以上方法
sizeof(int)
代表数组中每个元素的类型
N
代表数组的元素个数
即,程序在执行过程中,到一定的执行环境下,动态的分配内存。
# malloc和free
头文件:
stdlib
原型:
void* malloc(size_t size)
所以需要根据实际你需要的类型对其强制类型转换
返回值: 成功时,返回指向新分配内存的指针。为避免内存泄漏,必须用 free() 或 realloc() 解分配返回的指针。
失败时,返回空指针(NULL)
参数:size - 要分配的字节数
头文件:
stdlib
原型:
void free( void* ptr );
参数:指向要解分配的内存的指针
返回值:无
此函数接收空指针(并对其不处理)以减少特例的数量。不管分配成功与否,分配函数返回的指针都能传递给 free()
malloc():的意义是向 堆区 要了一块sizeof(int) * N
这么大的空间
free():将申请来的空间的 首地址 还给“系统”,只要申请到了空间就一定要归还
# C中*符号的作用
# 定义指针
C中的普通变量加 * 号,表示指针变量
int q;//普通整型
int *q;// 整型指针
# 间接运算
当已具有一个指针,并且希望获取它所引用的对象时,使用间接运算符
int *q = &a;
// 在指针(一个地址)前面加上*,得到的是该地址上的对象
a = *(*q);
# 双目运算符-乘法
x = y*z;
# C中的typedef 关键字
可以使用它来为类型取一个新的名字
typedef unsigned char BYTE;
在这个类型定义之后,标识符 BYTE 可作为类型 unsigned char 的缩写
# exit()函数
- 头文件 stdlib.h
- 功能,关闭所有文件,终止正在执行的进程
- exit(1) 表示异常退出
- exit(x)x不为0,表示异常退出
- exit(0)表示正常退出
- 返回值 若在主函数中,则会退出并返回结果
# exit函数和return的区别
- return返回函数值,是关键字; exit 是一个函数。
- return是语言级别的,它表示了调用堆栈的返回;而exit是系统调用级别的,它表示了一个进程的结束。
- return是函数的退出(返回);exit是进程的退出。
- return是C语言提供的,exit是操作系统提供的(或者函数库中给出的)。
- return用于结束一个函数的执行,将函数的执行信息传出个其他调用函数使用;exit函数是退出应用程序,删除进程使用的内存空间,并将应用程序的一个状态返回给OS(操作系统),这个状态标识了应用程序的一些运行信息,这个信息和机器和操作系统有关,一般是 0 为正常退出,非0 为非正常退出。
- 非主函数中调用return和exit效果很明显,但是在main函数中调用return和exit的现象就很模糊,多数情况下现象都是一致的。
# -> 和 .
-> 适用于结构体指针变量调用某个成员变量。
. 适用于结构体变量调用某个成员变量。