C语言基础day10

复习

什么是结构体

将一些属性封装成一个类型。

结构体的本质是类型。

如何定义结构体变量

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; 野指针
当一个指针变量被定义出来,没有赋值,那么指针的指向是不确定的,要叫野指针。非常危险。

野指针被使用时不一定崩溃。一定要避免野指针。

开发中程序的错误:

  1. 编译错误,最好解决,语法错误。

  2. 程序崩溃 还算好解决,因为好找到问题在哪里。

  3. 结果不对 最难解决,因为没有线索。

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;
}
  • 1
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

encounter♌

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值