# 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);
}

# 游戏结果

1652956487732 1652956501899

# C 指针问题

# 什么是指针?

程序中数据被保存在内存中,每个内存都具有专属的地址,这个地址就是指针,为了保存一个数据在内存中的地址,就需要专门用来保存内存地址的变量,这种变量被称为指针变量

# 为什么需要指针?

  1. 指针是内存地址的抽象概念,内存地址本身计算机不可避免
  2. 指针可以用来传递数据源,以节省空间和时间
  3. 可以使得不同区域的代码能够轻易的共享内存数据
  4. C语言中链表、二叉树等结构体需要用指针来构建
  5. 某些必须使用到指针的操作,如申请的堆内存

# 指针类型有哪些?

c语言的指针类型跟普通类型一样,包括int、float、double、数组等

只是定义的时候需要加 * 号。

# 如何理解C语言中的* ?

指针篇(一篇让你完全搞懂指针) (opens new window)

  • int p 定义的普通的int类型的整型变量
    • p中存放的是某个具体的数值
  • int *p定义的一个int * 类型的整型指针变量,p指向一个普通整型的地址
    • p中存放的是某个地址
    • 该地址指向的内存存放的是某个具体的数值
  • int **p ,可以认为是指向一个指针变量的地址,该指针变量指向某个地址
    • p中存放的是指针变量的地址
    • 指针变量的地址映射的内存存放的是某个地址
    • 某个地址中存放的具体的数值
  • int ***p 指向某个指针变量地址的变量的地址
    • p中存放的是存放指针变量的地址的指针的地址

# C中&符号的作用

  1. 指针赋值

    1. 当使用&时,将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;
    }
    
  2. 二目运算符中的按位与

    1. 单目是只需要一个操作数的意思 比如 a++ a-- *a &a
    2. 双目是需要两个操作数的意思 比如 a+b a-b a*b a/b a%b
    3. 三目是需要三个操作数的意思 比如 a=c>b?c:b
  3. 当出现&&时用于逻辑与

# C 中函数传递的几种方式

  • 值传递
  1. 变量的值传递给函数的形式参数,实际就是用变量的值来新生成一个形式参数
  2. 形参是实参的拷贝,改变形参的值并不会影响外部实参的值。从被调用函数的角度来说,值传递是单向的(实参->形参),参数的值只能传入,不能传出。
  3. 当函数内部需要修改参数,并且不希望这个改变影响调用者时,采用值传递。
  4. 函数里对形参的改变不会影响到函数外的变量的值
  • 地址传递
    1. 把变量的地址赋给函数里形式参数的指针,使指针指向真实的变量的地址
    2. 对指针所指地址的内容的改变能反映到函数外,能改变函数外的变量的值
    3. 形参为指向实参地址的指针,当对形参的指向操作时,就相当于对实参本身进行的操作
  • 引用传递
    1. 通过指针来实现的,能达到使用的效果如传址,可是使用方式如传值。
    2. 形参相当于是实参的“别名”,对形参的操作其实就是对实参的操作。
    3. 在引用传递过程中,被调函数的形式参数虽然也作为局部变量在栈中开辟了内存空间,但是这时存放的是由主调函数放进来的实参变量的地址。
    4. 被调函数对形参的任何操作都被处理成间接寻址,即通过栈中存放的地址访问主调函数中的实参变量。
    5. 被调函数对形参做的任何操作都影响了主调函数中的实参变量。

如果传值的话,会生成新的对象,花费时间和空间,而在退出函数的时候,又会销毁该对象,花费时间和空间。

因而如果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操作符

C/C++ | sizeof (opens new window)

# 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的区别

  1. return返回函数值,是关键字; exit 是一个函数。
  2. return是语言级别的,它表示了调用堆栈的返回;而exit是系统调用级别的,它表示了一个进程的结束。
  3. return是函数的退出(返回);exit是进程的退出。
  4. return是C语言提供的,exit是操作系统提供的(或者函数库中给出的)。
  5. return用于结束一个函数的执行,将函数的执行信息传出个其他调用函数使用;exit函数是退出应用程序,删除进程使用的内存空间,并将应用程序的一个状态返回给OS(操作系统),这个状态标识了应用程序的一些运行信息,这个信息和机器和操作系统有关,一般是 0 为正常退出,非0 为非正常退出。
  6. 非主函数中调用return和exit效果很明显,但是在main函数中调用return和exit的现象就很模糊,多数情况下现象都是一致的。

# -> 和 .

-> 适用于结构体指针变量调用某个成员变量。

. 适用于结构体变量调用某个成员变量。