复习
什么是结构体
将一些属性封装成一个类型。
结构体的本质是类型。
如何定义结构体变量
struct Node
{
//属性
int age;
char name[20];
};
int main()
{
struct Node n;//struct Node是定义变量时完整的类型名 n结构体变量的名字
return 0;
}
访问成员变量
. 成员运算符 是结构体变量的成员的本身,如果成员是数组,那么计算得到的结果就是数组名。
int main()
{
struct Node n;//struct Node是定义变量时完整的类型名 n结构体变量的名字
n.age = 10;
strcpy(n.name, "hello");
return 0;
}
结构体指针
-> 成员运算符 ->左值是结构体指针变量 .左值是结构体变量
int main()
{
struct Node n;//struct Node是定义变量时完整的类型名 n结构体变量的名字
struct Node *p = &n;
p->age = 10;
strcpy(p->name, "hello");
return 0;
}
结构体数组
int main()
{
struct Node n = {10, "hello"};//初始化成员的顺序必须和定义成员的顺序一致
struct Node ns[2] = {
{11, "xiaoming"},
{12, "xiaofang"}
};
return 0;
}
结构体嵌套
//date日期 data数据
struct Date
{
int year;
int month;
int day;
};
struct Student
{
char name[20];
struct Date birthday;//使用结构体类型的变量作为成员
};
结构体大小计算
为了方便计算结构体的大小,我们会在逻辑上对它分行。
最大成员的大小就是“一行”的大小。
如果一个成员自己不能占满“一行”,要么把它全放在左边,要么把它全放在右边。
32位系统,“一行”最大就是4。
笔试题的时候:
1 如果说是64位系统,就是告诉我们“一行”最大时8
2 直接告诉我们是以几个字节对齐。
如果没有给以上的信息,默认就是32位。
作业1:
1 定义时间结构体: 包含年、月、日成员
2 完成set_date函数
函数声明如下: void set_date(struct date *p, int y, int m, int d);
3 完成print_date函数
函数声明如下: void print_date(struct date *p);
4 完成main函数,
实现如下功能:
1) 定义 时间结构体变量 d1
2) 输入时间,并使用set_date函数设置给变量d1
3) 使用print_date函数输出设置的时间
#include <stdio.h>
struct Date
{
int year;
int month;
int day;
};
void setDate(struct Date* p, int y, int m, int d);
void printDate(struct Date* p);
int main()
{
struct Date d;
setDate(&d, 2022, 1, 15);
printDate(&d);
return 0;
}
void setDate(struct Date* p, int y, int m, int d)
{
p->year = y;
p->month = m;
p->day = d;
}
void printDate(struct Date* p)
{
printf("%d %d %d\n", p->year, p->month, p->day);
}
作业2:
定义学生结构体数组,编写函数对这个数组根据学生成绩进行降序排序,编写函数输出排序后的学生信息
#include <stdio.h>
struct Student
{
char name[20];
int score;
};
void sort(struct Student* arr, int len);
void printStudents(struct Student* arr, int len);
int main()
{
struct Student students[3] = {
{"xiaoming", 90},
{"xiaojun", 95},
{"xiaolv", 80}
};
sort(students, 3);
printStudents(students, 3);
return 0;
}
//结构体变量可以给结构体变量赋值
void sort(struct Student* arr, int len)
{
int i, j;
for(i = 0;i < len - 1;i++)
{
for(j = 0;j < len-1-i;j++)
{
if(arr[j].score < arr[j+1].score)
{
struct Student t = arr[j];
arr[j] = arr[j+1];
arr[j+1] = t;
}
}
}
}
void printStudents(struct Student* arr, int len)
{
int i;
for(i = 0;i < len;i++)
{
printf("%s %d\n", arr[i].name, arr[i].score);
}
}
1、内存分配
1. 内存分区(变量可能被分配的内存分区)
栈:普通的局部变量,都在栈空间。 所在的作用域开始执行的时候创建变量,作用域执行完自动释放。
堆:自己创建和删除变量。 堆空间的变量生命周期自定义。如果我们不释放堆空间变量,就算到程序结束堆空间 的内存也不会被释放。堆空间不释放,我们叫“内存泄漏”。
静态:全局变量在静态区。程序开始执行创建变量,程序结束之后释放变量。
不同的内存空间对变量的生命周期的管理方式不一样,我们选择把变量定义在哪个内存空间,取决于我们想让变量有怎么样的生命周期。
2. 堆空间内存的申请与释放
1) 申请内存: void *malloc(size_t size);
参数:长整型,要在堆空间申请内存的大小。
返回值:是申请内存的首字节地址。void* 是无类型的指针,它可以被转换成任何类型的指针。
2) 释放内存: void free(void *p);
参数:要释放的堆空间内存的地址。
3. 使用注意事项:
1) malloc 和 free 成对去写。(malloc 申请的空间,使用结束后,要及时的free )
2) malloc 申请内存,会成功或失败,如果成功,则可以使用申请来的堆空间。 如果失败,会返回 NULL
NULL就是0 空指针。
3) 在堆内存被释放掉之后,及时将指针 p 归零,以防出现使用这块已经被释放掉的内存,造成不必要的风险。
示例1:
#include <stdio.h>
#include <stdlib.h> //malloc 和free的头文件
int main()
{
int *p = (int *)malloc( 10*sizeof(int) );//10*sizeof(int)传达的信息是申请int类型的数组,10个元素
//强制类型转换,malloc返回的void*地址,仅仅是地址,不能直接使用。所以使用强制类型转换成我们需要的类型
//p存放了堆空间数组的首地址。
if(NULL == p)//判断申请失败 NULL == p这种写法,为了避免不小心把==写成=
{
printf("error\n");
return 0;
}
int i;
//遍历数组,输入值
for(i=0; i<10; i++)
{
scanf("%d", &p[i]);//p存放了数组的首地址 p[i]数组的i元素,是int类型, 所以要对数组的i元素使用scanf赋值,需要对元素取地址
//代码还可以写成 scanf("%d", p+i);
}
//遍历数组并打印
for(i=0; i<10; i++)
{
printf("%d, ", p[i]);
}
free(p);//堆空间的内存用完了,要释放
p = NULL;//安全操作
return 0;
}
2、空指针和野指针
char *p = NULL; 空指针 NULL 就是0 所以也可以写char *p = 0;
不管是初始化成NULL 还是赋值成NULL,都是空指针。
当一个指针变量被定义出来以后,如果不能马上指向一个变量,那么应该让它指向NULL,可以将NULL理解成空内存。
空指针一旦使用程序会崩溃。
当指针不知道该指向谁的时候,赋值成空指针。
char* p; 野指针
当一个指针变量被定义出来,没有赋值,那么指针的指向是不确定的,要叫野指针。非常危险。
野指针被使用时不一定崩溃。一定要避免野指针。
开发中程序的错误:
-
编译错误,最好解决,语法错误。
-
程序崩溃 还算好解决,因为好找到问题在哪里。
-
结果不对 最难解决,因为没有线索。
3、俄罗斯方块预备
1. 宏定义(C语言语法)
无参宏,给常量起名字
常量本身没有逻辑含义 1 5 100 我们都不知道要干什么
//define是宏定义的关键字
//#define 宏名(宏名不能包含空格) 宏值(常量)
//STUDENT_NUM 宏名(为了有效的区分变量名和宏名,宏名一般都大写) 45 宏值
//这个宏的意义 是给 常量45 起名为 STUDENT_NUM,在代码中需要写常量45的地方,都写成宏名STUDENT_NUM
#define STUDENT_NUM 45
#define CHAIR_NUM 45
#define PC_NUM 45
/*
宏定义的意义
1 增强代码可读性,把没有逻辑意义的常量,替换成又逻辑意义的标识符
2 便于代码维护,好修改。
*/
for(i = 0;i < PC_NUM;i++)//如果写成for(i = 0;i < 45;i++) 不能很直观的明白45是什么意思
{
}
在预处理的时候,编译器会把所有的宏名替换成宏值。 无脑替,无轮我们写的宏值是否合理,都替换。
#include <stdio.h>
#define STUDENT_NUM 10
int main()
{
int num = STUDENT_NUM;
printf("%d\n", num);
return 0;
}
雕虫小技
#include <stdio.h>
void printArr(int* arr, int len, int fst, int sec)
{
int i;
for(i = 0;i < len;i++)
{
if(i==fst || i==sec)
{
printf("\033[1;33m%d\033[0m ", arr[i]);
}
else
{
printf("%d ", arr[i]);
}
}
printf("\n");
}
void sort(int* arr, int len)
{
int i, j;
for(i = 0;i < len-1;i++)//外层循环反复冒泡
{
for(j = 0;j < len-1-i;j++)//内层循环冒一个泡
{
if(arr[j] > arr[j+1])
{
int t = arr[j];
arr[j] = arr[j+1];
arr[j+1] = t;
}
usleep(500000);
system("clear");
printArr(arr, len, j, j+1);
}
}
}
int main()
{
int a[10] = {12,2,31,14,25,86,17,8,92,20};
sort(a, 10);
int i;
for(i = 0;i < 10;i++)
{
printf("%d ", a[i]);
}
printf("\n");
return 0;
}
2. 延时函数和清屏函数
Linux系统函数
usleep(50000); //50毫秒
system(“clear”);
3. 打印特效(不需要掌握)
Linux控制台特殊效果 linux下printf的特殊用法
1)打印颜色
printf("\033[1;33m★\033[0m");
\033 打印属性设置
[1; 打印亮度 (1 高亮 2 暗淡)
■ □ ◆ ◇ ◎ ○ ★ ☆ № § △ ▲ → ↓
属性列表如下:
\1. 通用格式控制:
0 重置所有属性
1 高亮/加粗
2 暗淡
4 下划线
5 闪烁
7 反转
8 隐藏
\2. 前景色:
30 黑色
31 红色
32 绿色
33 黄色
34 蓝色
35 品红
36 青色
37 白色
void drowPoint(int x, int y)
{
printf("\033[%d;%dH", y+1, x*2+1);
printf("\033[1;36m■ \033[0m");
}
刷新屏幕
fflush(stdout);//清空缓存,将图形打印到控制台上
//输入缓存 stdin
//输出缓存 stdout
printf("\n");//\n有清理输出缓存的作用
在俄罗斯方块中,我们不用\n清理输出缓存
4. 非阻塞输入(不需要掌握)
scanf
getchar
gets
它们都是阻塞型输入,当程序执行到输入函数的时候,程序会停在那里等待用户输入。这种特性不适用与俄罗斯方块的输入。
非阻塞型输入和操作系统有密切关系。
system( STTY_US TTY_PATH ); // 直接识别输入的字符,不用按回车 程序开始时调用
system( STTY_DEF TTY_PATH ); //恢复输入状态 程序结束时调用
#include <fcntl.h>
#define TTY_PATH "/dev/tty"
#define STTY_US "stty raw -echo -F "
#define STTY_DEF "stty -raw echo -F "
//输入 ctrl+c函数返回整数 3
int get_char() //能实现非阻塞IO, 如果没有按下按键,程序继续往下走
{
fd_set rfds;
struct timeval tv;
int ch = 0;
FD_ZERO(&rfds);
FD_SET(0, &rfds);
tv.tv_sec = 0;
tv.tv_usec = 10; //设置等待超时时间
//检测键盘是否有输入
if (select(1, &rfds, NULL, NULL, &tv) > 0)
{
ch = getchar();
}
return ch;
}
5.预热
1)在屏幕上移动一个方块
2)在屏幕上显示一个图形 L型
3)尝试移动这个图形
项目步骤
锚点:图形在屏幕上表示位值的点。
1. 做图形库
用二维数组表示
//坐标点类型
struct Point
{
int x;
int y;
};
struct Point shapeLib[19][4] = {
{{0,0},{-1,0},{1,0},{2,0}},//横条
{{0,0},{0,-1},{0,1},{0,2}},//竖条
{{0,0},{-1,-1},{-1,0},{0,-1}},//方块
{{0,0},{0,-1},{0,-2},{1,0}},//正L1
{{0,0},{0,1},{1,0},{2,0}},//正L2
{{0,0},{-1,0},{0,1},{0,2}},//正L3
{{0,0},{0,-1},{-1,0},{-2,0}},//正L4
{{0,0},{-1,0},{0,-1},{0,-2}},//反L1
{{0,0},{0,-1},{1,0},{2,0}},//反L2
{{0,0},{1,0},{0,1},{0,2}},//反L3
{{0,0},{-1,0},{-2,0},{0,1}},//反L4
{{0,0},{-1,0},{1,0},{0,-1}},//T1
{{0,0},{0,1},{0,-1},{1,0}},//T2
{{0,0},{-1,0},{1,0},{0,1}},//T3
{{0,0},{-1,0},{0,-1},{0,1}},//T4
{{0,0},{1,0},{0,-1},{-1,-1}},//正Z1
{{0,0},{1,-1},{0,1},{1,0}},//正Z2
{{0,0},{1,-1},{-1,0},{0,-1}},//反z1
{{0,0},{-1,-1},{-1,0},{0,1}}//反Z2
};
2. 创建新图形
void createShap();
在初始位置随机生成一个图形
3.下落
//自然下落
int moveDownSpeed = 0;//因为每一帧下落一个单位太快,所以做一个技术,若干帧之后下落一个单位
void moveDown()
{
if(++moveDownSpeed == 5)
{
position.y++;
moveDownSpeed = 0;
}
}
4.碰撞
1)边界
5. 消行
#include <stdio.h>
int main()
{
int arr[17] = {1,0,2,0,3,2,3,0,0,0,6,0,0,5,4,0,-1};
int mark[17] = {0};//标记每个元素要移动的长度
int i;
int count = 0;
//给mark数组赋值
for(i = 0;i < 17;i++)
{
if(arr[i] == 0)
{
count++;
mark[i] = -1;
}
else
{
mark[i] = count;
}
}
//根据mark的标记移动数组
for(i = 0;i < 17;i++)
{
int len = mark[i];
if(mark[i] == -1)
{
continue;
}
if(len > 0)
{
arr[i-len] = arr[i];
}
}
for(i = 0;i < count;i++)
{
arr[16-i] = -1;
}
for(i = 0;i < 17;i++)
{
printf("%d ", arr[i]);
}
printf("\n");
return 0;
}