C语言---认识动态内存管理并实现一个动态通讯录:静态通讯录别来沾边

news2024/11/15 17:32:22

文章目录

  • 前言
  • 🌟一、为什么存在动态内存分配
  • 🌟二、动态内存函数的介绍
    • 🌏2.1.malloc函数+free函数
    • 🌏2.2.calloc函数+free函数
    • 🌏2.3.realloc函数
  • 🌟三、常见的动态内存错误
    • 🌏3.1.对NULL指针的解引用操作
    • 🌏3.2.对动态开辟空间的越界访问
    • 🌏3.3.对非动态开辟内存使用free释放
    • 🌏3.4.使用free释放一块动态开辟内存的一部分
    • 🌏3.5.对同一块动态内存多次释放
    • 🌏3.6.动态开辟内存忘记释放(内存泄漏)
  • 🌟四、优化通讯录
    • 🌏4.1.优化前后通讯录的创建
    • 🌏4.2.优化前后通讯录的初始化
    • 🌏4.3.优化后通讯录的增加人数
    • 🌏4.4.优化后通讯录的退出
    • 🌏4.5.完整的通讯录
  • 🌟五、一些笔试题
  • 😽总结


前言

👧个人主页:@小沈熬夜秃头中୧⍤⃝❅
😚小编介绍:欢迎来到我的乱七八糟小星球🌝
📋专栏:C语言学习
🔑本章内容:动态内存管理
送给各位💌:当你的能力还驾驭不了你的目标时那你就应该沉下心来历练
记得 评论📝 +点赞👍 +收藏😽 +关注💞哦~


提示:以下是本篇文章正文内容,下面案例可供参考

🌟一、为什么存在动态内存分配

我们已经掌握的内存开辟方式有:

int val = 20;在栈空间上开辟四个字节
char arr[10] = {0};在栈空间上开辟10个字节的连续空间

但是上述的开辟空间的方式有两个特点:
1.空间开辟大小是固定的。
⒉.数组在申明的时候,必须指定数组的长度,它所需要的内存在编译时分配。
但是对于空间的需求,不仅仅是上述的情况。有时候我们需要的空间大小在程序运行的时候才能知道,那数组的编译时开辟空间的方式就不能满足了。这时候就只能试试动态存开辟了。

在这里插入图片描述

🌟二、动态内存函数的介绍

🌏2.1.malloc函数+free函数

C语言提供了一个动态内存开辟的函数:

void*malloc (size_t size);
这个函数向内存申请一块连续可用的空间,并返回指向这块空间的指针。

  • 如果开辟成功,则返回一个指向开辟好空间的指针。
  • 如果开辟失败,则返回一个NULL指针,因此malloc的返回值一定要做检查。
  • 返回值的类型是void,所以malloc函数并不知道开辟空间的类型,具体在使用的时候使用者自己来决定。*
  • 如果参数size为0,malloc的行为是标准是未定义的,取决于编译器。
    在这里插入图片描述
    C语言提供了另外一个函数free,专门 是用来做动态内存的释放和回收的,函数原型如下:

void free (void* ptr);
free函数用来释放动态开辟的内存。

  • 如果参数ptr指向的空间不是动态开辟的,那free函数的行为是未定义的。
  • 如果参数ptr是NULL指针,则函数什么事都不做。
    malloc和free都声明在stdlib.h头文件中。
    各位不要忘了我们之前学数组和指针的时候学过(p+i)==p[i],所以也可以写成p[i]
#include<stdio.h>
#include<errno.h>
#include<string.h>
#include<stdlib.h>
int main()
{
	//申请
	//我们放整形时malloc是void*所以强制类型转换赋值给int* p
	int* p=(int*)malloc(20);//malloc这个函数一旦申请空间失败就会返回一个空指针
	if (p == NULL)
	{
		printf("%s\n", strerror(errno));
		return 1;
	}
	//开辟成功 --- 使用
	int i = 0;
	for (i = 0; i < 5; i++)
	{
		*(p + i) = i + 1;//为了防止p的地址被改变导致后面释放出错所以我们不用p++而是p+i
	}
	for (i = 0; i < 5; i++)
	{
		printf("%d", *(p + i));
	}
	//释放
	free(p);//free函数并不会让p置为空指针
	//free将所开辟的空间释放还给系统
	p = NULL;//避免了野指针
	return 0;
}

在这里插入图片描述

我们可以在调试内存中看到我们把所想要放入的值放进去了
在这里插入图片描述

注意:malloc申请空间时,里面放的都是随机值

🌏2.2.calloc函数+free函数

C语言还提供了一个函数叫ca1loc,cal1oc函数也用来动态内存分配。原型如下:

void* calloc (size_t num,size_t size);

  • 函数的功能是为num个大小为size的元素开辟一块空间,并且把空间的每个字节初始化为0。
  • ·与函数ma1loc的区别只在于calloc会在返回地址之前把申请的空间的每个字节初始化为全0。
    在这里插入图片描述
#include<stdio.h>
#include<errno.h>
#include<stdlib.h>
int main()
{
	//开辟
	int* p = (int*)calloc(10, sizeof(int));
	if (p == NULL)
	{
		printf("calloc()---%s\n", strerror(errno));
	}
	//使用
	int i = 0;
	for (i = 0; i < 10; i++)
	{
		printf("%d ", p[i]);
	}
	//释放
	free(p);
	p = NULL;
	return 0;
}

根据运行结果我们可以看出calloc是由初始化的而malloc是随机值
在这里插入图片描述

注意:
calloc和malloc的对比:

1.参数不一样
⒉都是在堆区上申请内存空间,但是malloc不初始化,calloc会初始化为0如果要初始化,就使用calloc不需要初始化,就可以使用malloc

🌏2.3.realloc函数

realloc函数的出现让动态内存管理更加灵活。
有时会我们发现过去申请的空间太小了,有时候我们又会觉得申请的空间过大了,那为了合理的时候内存,我们一定会对内存的大小做灵活的调整。那rea1loc函数就可以做到对动态开辟内存大小的调整。

函数原型如下:

void* rea1loc (void* ptr,size_t size);

  • ptr是要调整的内存地址
  • size调整之后新大小(这里是字节哦~)
  • 返回值为调整之后的内存起始位置。
  • 这个函数调整原内存空间大小的基础上,还会将原来内存中的数据移动到新的空间。

在这里插入图片描述
在这里插入图片描述

#include<stdio.h>
#include<errno.h>
#include<stdlib.h>
int main()
{
	//开辟
	int* p = (int*)malloc(20);
	if (p == NULL)
	{
		printf("%s\n", strerror(errno));
		return 1;
	}
	//使用
	int i = 0;
	for (i = 0; i < 5; i++)
	{
		p[i] = i + 1;
	}
	int* ptr = (int*)realloc(p, 40);//这里不能用p接受哦因为realloc返回空指针时,p就被赋值为空指针原来的20个字节的空间都找不到了
	if (ptr != NULL)
	{
		p = ptr;
	}
	else
	{
		printf("realloc:%s\n", strerror(errno));
		return 1;
	}
	for (i = 5; i < 10; i++)
	{
		p[i] = i+1;
	}
	for (i = 0; i < 10; i++)
	{
		printf("%d ", p[i]);
	}
	//释放
	free(p);
	p = NULL;
	return 0;
}

🌟三、常见的动态内存错误

🌏3.1.对NULL指针的解引用操作

可能会出现对NULL指针的解引用操作
所以malloc函数的返回值要判断

#include<stdio.h>
#include<stdlib.h>
int main()
{
	int* p = (int*)malloc(20);
	int i = 0;
	for (i = 0; i < 5; i++)
	{
		p[i] = i;
	}
	free(p);
	p = NULL;
	return 0;
}

🌏3.2.对动态开辟空间的越界访问

在这里插入图片描述

#include<stdio.h>
#include<stdlib.h>
int main()
{
	int* p = (int*)malloc(20);
	if (p == NULL)
	{
		printf("%s\n", strerror(errno));
		return 1;
	}
	int i = 0;
	//malloc开辟了20个字节的空间但是这里要访问的却是40个字节的空间发生了越界访问
	for (i = 0; i < 10; i++)
	{
		p[i] = i;
	}
	free(p);
	p = NULL;
	return 0;
}

🌏3.3.对非动态开辟内存使用free释放

在这里插入图片描述

#include<stdio.h>
#include<stdlib.h>
int main()
{
	int arr[10] = { 1,2,3,4,5,6,7,8,9,10 };
	int* p = arr;
	free(p);
	p = NULL;
	return 0;
}

🌏3.4.使用free释放一块动态开辟内存的一部分

在这里插入图片描述

#include<stdio.h>
#include<stdlib.h>
int main()
{
	int* p = (int*)malloc(20);
	if (p == NULL)
	{
		printf("%s\n", strerror(errno));
		return 1;
	}
	int i = 0;
	for (i = 0; i < 10; i++)
	{
		*p = i + 1;
		p++;
	}
	free(p);
	p = NULL;
	return 0;
}

🌏3.5.对同一块动态内存多次释放

这种问题可能会出现在一些事情上,假设A程序猿开辟一块空间后释放,但是B程序员并不知道,可能会导致二次释放。所以我们可以将p==NULL置为空指针这样就不会报错啦

#include<stdio.h>
#include<stdlib.h>
int main()
{
	int* p = (int*)malloc(20);
	if (p == NULL)
	{
		printf("%s\n", strerror(errno));
		return 1;
	}
	//释放
	free(p);
	p = NULL;//我们释放掉把它置为空指针,p为空指针,这样就不会报错
	//释放
	free(p);
	p = NULL;
	return 0;
}

🌏3.6.动态开辟内存忘记释放(内存泄漏)

malloc calloc realloc所申请的空间,如果不想使用,需要free释放如果不使用free释放;程序结束之后,也会由操作系统回收;
如果不使用free释放,程序也不结束就会发生内存泄露

#include<stdio.h>
void test()
{
	int* p = (int*)malloc(20);
	//使用
}
int main()
{
	test();
	//这个函数一旦返回p就被销毁了,并没有把地址返回来
	//出了test函数来到主函数内部,主函数并不知道这个内存在哪里所以并没被释放
	return 0;
}

🌟四、优化通讯录

🌏4.1.优化前后通讯录的创建

首先创建一个通讯录我们想起之前写过的静态通讯录

typedef struct PeoInfo
{
	char name[MAX_NAME];
	int age;
	char sex[MAX_SEX];
	char tele[MAX_TELE];
	char addr[MAX_ADDR];
}PeoInfo;
typedef struct Contact
{
	PeoInfo data[MAX];
	int sz;
}Contact;

那我们动态版本的怎么写呢?我们让它满足几点要求:
1.默认能够存放3个人的信息
2.不够的话,每次增加2个人信息
这样的话我们的动态版本不够的话每次需要增加两个人的信息所以就不能是数组形式了不然会出现问题malloc开辟的空间的起始地址交给一个指针维护;因为i要扩容我们要定义一个变量(capacity)来记录容量,还有定义一个变量(sz)来记录通讯录中的有效信息

typedef struct PeoInfo
{
	char name[MAX_NAME];
	int age;
	char sex[MAX_SEX];
	char tele[MAX_TELE];
	char addr[MAX_ADDR];
}PeoInfo;
//静态版本
typedef struct Contact
{
	PeoInfo *data;//不够的话每次需要增加两个人的信息所以这里不能用数组会发生问题
	//malloc开辟的空间的起始地址交给一个指针维护
	//data指向了存向数据的空间
	int sz;//记录通讯录中的有效信息
	int capacity;//记录通讯录当前的容量
}Contact;

🌏4.2.优化前后通讯录的初始化

接下来就是初始化啦~对于数组我们初始化使用memset函数把里面的每个元素都设置为0

void InitContact(Contact* pc)
{
	pc->sz=0;
	//pc->date;//date是一个数组是一块连续的空间,数组名是地址不可以改成0
	//所以应该把pc所指向通讯录的date数组里的值改成0
	memset(pc->data, 0, sizeof(pc->data));
}

但对于动态内存的初始化,我们需要提前在之前静态的版本上再定义一个最开始默认值放在contact.h,这样方便我们日后更改默认值和扩容量

#define DEFAULT_SZ 3//初始默认值为3
#define INC_SZ 2//每次扩容2个

在这里插入图片描述

void InitContact(Contact* pc)
{
	pc->data = (PeoInfo*)malloc(DEFAULT_SZ * sizeof(PeoInfo));
	//最开始默认大小,但希望我们日后可以随时改变所以我们定义一个DEFAULT_SZ和INC_SZ(扩容)
	if (pc == NULL)
	{
		printf("通讯录初始化失败:%s\n", strerror(errno));
		return;
	}
	pc->sz = 0;
	pc->capacity = DEFAULT_SZ;//默认初始值
}

🌏4.3.优化后通讯录的增加人数

不够就增容,所以我们发现主要和增加有关系,所以我们需要判断一下是否通讯录的人数超过三个是否需要扩容,所以我们分装一个函数来完成这个行为(用realloc来完成)
注意:
这里需要注意不然容易出现bug我们扩容后,一定要pc->data = ptr;不然实际上的data并没有扩容我们超过三个人的信息就会发生越界访问,INC_SZ就是我们最开始在contact.h中定义的扩容值,所以扩容后这个记录capacity当前容量的变量会+=2;我们还要注意一点扩容失败返回0,扩容成功,或者不需要扩容,返回1

int CheckCapacity(Contact* pc)
{
	if (pc->sz == pc->capacity)
	{
		PeoInfo*ptr=realloc(pc->data, (pc->capacity + 2) * sizeof(PeoInfo));
		if (ptr == NULL)
		{
			printf("CheckCapacity:%s\n", strerror(errno));
			return 0;
		}
		else
		{
			pc->data = ptr;//返回值为调整之后的内存起始位置
			pc->capacity += INC_SZ;
			printf("增容成功,当前容量:%d\n", pc->capacity);
			return 1;
		}
	}
}
void AddContact(Contact* pc)
{
	if(CheckCapacity(pc)==0)
	{
		printf("空间不够,扩容失败\n");
		return 0;
	}
	else
	{
		printf("请输入名字>:");
		scanf("%s", pc->data[pc->sz].name);
		printf("请输入年龄>:");
		scanf("%d", &(pc->data[pc->sz].age));
		printf("请输入性别>:");
		scanf("%s", pc->data[pc->sz].sex);
		printf("请输入电话>:");
		scanf("%s", pc->data[pc->sz].tele);
		printf("请输入地址>:");
		scanf("%s", pc->data[pc->sz].addr);
		pc->sz++;
		printf("输入完成\n");
	}
}

🌏4.4.优化后通讯录的退出

前面的修改查找删除我们不需要做更改所以我们来到最后一项对于我们malloc开辟的内存空间最后我们要释放,所以我们要在静态版本的基础上进行修改。我们分装一个DestroyContact函数,然后在contact.h中声明再在contact.c中实现
在这里插入图片描述
在这里插入图片描述

释放完成后,我们将data置为NULL,capacity置为0,sz置为0

void DestroyContact(Contact* pc)
{
	free(pc->data);
	pc->data = NULL;
	pc->capacity = 0;
	pc->sz = 0;
	printf("释放内存\n");
}

🌏4.5.完整的通讯录

//contact.h
#define _CRT_SECURE_NO_WARNINGS 1
#include<stdio.h>
#define MAX 100
#define MAX_NAME 20
#define MAX_SEX 5
#define MAX_TELE 12
#define MAX_ADDR 30
//表示一个人信息
typedef struct PeoInfo
{
	char name[MAX_NAME];
	int age;
	char sex[MAX_SEX];
	char tele[MAX_TELE];
	char addr[MAX_ADDR];
}PeoInfo;
typedef struct Contact
{
	PeoInfo data[MAX];
	int sz;
}Contact;
//初始化通讯录
void InitContact(Contact* pc);
//增加指定联系人
void AddContact(Contact* pc);
//显示联系人信息
void ShowContact(Contact* pc);
//删除联系人信息
//void DelContact(pContact pc);
void DelContact(Contact* pc);
//查找指定联系人
void SearchContact(Contact* pc);
//修改指定联系人信息
void ModifyContact(Contact* pc);
//排序联系人
void SortContact(Contact* pc);
//contact.c
#define _CRT_SECURE_NO_WARNINGS 1
#include"contact.h"
#include<string.h>
void InitContact(Contact* pc)
{
	pc->sz=0;
	//pc->date;//date是一个数组是一块连续的空间,数组名是地址不可以改成0
	//所以应该把pc所指向通讯录的date数组里的值改成0
	memset(pc->data, 0, sizeof(pc->data));
}
void AddContact(Contact* pc)
{
	if (pc->sz == MAX)
	{
		printf("通讯录已满,无法增加\n");
		return;//在函数里面遇见return就返回了,因为是void所以不需要返回值
	}
	printf("请输入名字>:");
	//结构体对象用 . 
	//结构体指针用 ->
	scanf("%s", pc->data[pc->sz].name);//数组名本来就是一个地址所以不需要进行&操作
	printf("请输入年龄>:");
	scanf("%d", &(pc->data[pc->sz].age));//年龄是一个变量所以需要&操作
	printf("请输入性别>:");
	scanf("%s", pc->data[pc->sz].sex );
	printf("请输入电话>:");
	scanf("%s", pc->data[pc->sz].tele );
	printf("请输入地址>:");
	scanf("%s", pc->data[pc->sz].addr );
	pc->sz++;
	printf("输入完成\n");
}
void ShowContact(const Contact* pc)
{
	int i = 0;
	//printf("%10s %4s %5s %12s %30s\n", "姓名" "年龄" "性别" "电话" "地址");
	//打印标题
	printf("%-10s %-4s %-5s %-12s %-30s\n", "姓名", "年龄", "性别", "电话", "地址");
	//打印数据
	for (i = 0; i < pc->sz; i++)
	{
		printf("%-10s %-4d %-5s %-12s %-30s\n", 
			pc->data [i].name, pc->data[i].age, pc->data[i].sex, pc->data[i].tele, pc->data[i].addr);
	}
}
static int FindByName(Contact*pc,char name[])//加上static这个函数只能在所在.c文件中使用
{
	int i = 0;
	for (i = 0; i < pc->sz; i++)
	{
		if (strcmp(pc->data[i].name, name) == 0)
		{
			return i;//记录所删除联系人所在位置的下标
			break;
		}
	}
	if (i == pc->sz)//这里如果判断pos==0时可能是第一个元素因为i是从0开始了所以可以把pos开始赋值为-1
	{
		return -1;
	}
}
void DelContact(Contact* pc)
{
	char name[MAX_NAME] = { 0 };
	int i = 0;
	printf("请输入要删除指定联系人的名字\n");
	scanf("%s", &name);
	if (pc->sz == 0)
	{
		printf("通讯录为空,无法删除\n");
	}
	//删除
	//1.找到删除的指定联系人 - 位置 (下标)
	int pos = FindByName(pc, name);
	if (pos == -1)
	{
		printf("要删除的人不存在\n");
	}
	//2.删除 - 删除pos位置上的数据
	for (i = pos;i<pc->sz -1; i++)
	{
		pc->data[i] = pc->data[i + 1];
	}
	pc->sz--;//元素个数减少
	printf("删除成功\n");
}
void SearchContact(Contact* pc)
{
	//我们发现查找也需要遍历删除也需要所以为了简洁我们可以把这个遍历的数组分装成一个函数
	char name[MAX_NAME] = { 0 };
	printf("请输入要查找人的名字\n");
	scanf("%s", &name);
	int pos = FindByName(pc, name);
	if (pos == -1)
	{
		printf("要查找的人不存在\n");
	}
	//找到就打印
	printf("%-10s %-4s %-5s %-12s %-30s\n", "姓名", "年龄", "性别", "电话", "地址");
	printf("%-10s %-4d %-5s %-12s %-30s\n",
		pc->data[pos].name, 
		pc->data[pos].age, 
		pc->data[pos].sex, 
		pc->data[pos].tele, 
		pc->data[pos].addr);
}
void ModifyContact(Contact* pc)
{
	char name[MAX_NAME] = { 0 };
	printf("请输入修改指定联系人的名字\n");
	scanf("%s", &name);
	int pos = FindByName(pc, name);
	if (pos == -1)
	{
		printf("要修改的人不存在\n");
	}
	//存在就修改
	printf("请输入名字>:");
	scanf("%s", pc->data[pos].name);
	printf("请输入年龄>:");
	scanf("%d", &(pc->data[pos].age));
	printf("请输入性别>:");
	scanf("%s", pc->data[pos].sex);
	printf("请输入电话>:");
	scanf("%s", pc->data[pos].tele);
	printf("请输入地址>:");
	scanf("%s", pc->data[pos].addr);
	printf("修改成功\n");
}
//按照名字排序
int cmp_by_name(const void* e1, const void* e2)
{
	return strcmp(((PeoInfo*)e1)->name, ((PeoInfo*)e2)->name);
}
void SortContact(Contact* pc)
{
	qsort(pc->data, pc->sz, sizeof(PeoInfo),cmp_by_name);
	printf("排序成功\n");
}
//test.c
#define _CRT_SECURE_NO_WARNINGS 1
#include "contact.h"
void meau()
{
	printf("*****************************\n");
	printf("****1. add      2. del   ****\n");
	printf("****3. search   4. modify****\n");
	printf("****5. show     6. sort  ****\n");
	printf("****0. exit              ****\n");
	printf("*****************************\n");
}
enum Option
{
	EXIT,
	ADD,
	DEL,
	SEARCH,
	MODIFY,
	SHOW,
	SORT
};
int main()
{
	int input = 0;
	Contact con;
	InitContact(&con);
	do
	{
		meau();
		printf("请选择:>\n");
		scanf("%d", &input);
		switch (input)
		{
		case ADD:
			AddContact(&con);
			break;
		case DEL:
			DelContact(&con);
			break;
		case SEARCH:
			SearchContact(&con);
			break;
		case MODIFY:
			ModifyContact(&con);
			break;
		case SHOW:
			ShowContact(&con);
			break;
		case SORT:
			SortContact(&con);
			break;
		case EXIT:
			printf("退出通讯录\n");
			break;
		default:
			printf("选择错误\n");
			break;
		}
	} while (input);
	return 0;
}

🌟五、一些笔试题

1.调用GetMemory函数的时候,str的传参为值传递,p是str的临时拷贝,所以在GetMemory函数内部讲动态开辟空间的地址存放在p中的时候,不会影响str.所以GetMemory函数返回之后,str中依然是NULL指针。strcpy函数就会调用失败,原因是对NULL的解引用操作,程序会崩溃。

2.GetMemory函数内容malloc申请的空间没有机会释放,造成了内存泄露。

#include<stdio.h>
#include<string.h>
void GetMemory(char* p)//传值
{
	p = (char*)malloc(100);
}
void Test(void)
{
	char* str = NULL;
	GetMemory(str);
	strcpy(str,"hello world");
	printf(str);
}
int main()
{
	Test();
	return 0;
}

返回栈空间地址的问题
GetMemory函数内部创建的数组是临时的,虽然返回了数组的起始地址给了str,但是数组的内存出了
GetMemory函数就被回收了,而str依然保存了数组的起始地址,这时如果使用str,str就是野指针。

#include<stdio.h>
char* GetMemory(void)
{
	char p[] = "he1lo wor1d";
	return p;
}
void Test(void)
{
	char* str = NULL;
	str = GetMemory();
	printf(str);
}
int main()
{
	Test();
	return 0;
}

😽总结

请添加图片描述
😽Ending,今天的实现动态内存管理(上)+优化版通讯录+笔试题内容就到此结束啦~如果后续想了解更多,就请关注我吧,一键三连哦 ~

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/642823.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

SQL(--修改中--)

目录 一、基本介绍 二、常用函数 集合函数 字符串函数 MYSQL的日期和时间函数 SQL重要的内建日期函数 MYSQL重要的内建日期函数 条件判断函数 三、操作 单表查询 多表查询 使用正则表达式查询 添加&#xff1a; 修改&#xff1a; 删除&#xff1a; 四、…

Linux系统实现虚拟内存教程

Linux系统实现虚拟内存有两种方法&#xff1a;交换分区&#xff08;swap分区&#xff09;和交换文件&#xff0c; 一、交换文件 查看内存&#xff1a;free -m , -m是显示单位为MB&#xff0c;-g单位GB free -g 创建一个文件&#xff1a;touch命令用于修改…

【2023电工杯】A题 电采暖负荷参与电力系统功率调节的技术经济分析 30页论文及python代码

【2023电工杯】A题 电采暖负荷参与电力系统功率调节的技术经济分析 30页论文及python代码 1 题目 A题:电采暖负荷参与电力系统功率调节的技术经济分析 建设以新能源为主体的新型电力系统是应对全球气候变化挑战的重要举措。高比例新能源接入导致电力系统调节能力稀缺&#x…

电厂人员定位管理系统,厂区人员及车辆轨迹可循

随着科技的不断发展&#xff0c;室内定位技术已经逐渐成为电厂管理中不可或缺的一部分。在传统的变电站管理中&#xff0c;由于缺乏有效的定位技术&#xff0c;很难对设备、人员和物资进行精确的管理&#xff0c;导致了效率低下、成本高昂的问题。而现在&#xff0c;通过引入室…

企业必须知道:数字化官网已成为新发展格局

​如今我们生活在一个数据驱动发展的时代&#xff0c;不能顺应时代发展进步的企业就会落后和淘汰。一个新技术时代应运而生&#xff0c;一个数据主导的数字企业时代也必将应声而至。 社交媒体、移动设备、物联网和大数据引发的数字化趋势不仅改变了人们的生活方式而且要求企业…

工厂模式(四)

过气的&#xff0c;终究是过气了 上一章简单介绍了单例模式(三), 如果没有看过,请观看上一章 一. 工厂模式 引用 菜鸟教程里面的单例模式介绍: https://www.runoob.com/design-pattern/factory-pattern.html 工厂模式&#xff08;Factory Pattern&#xff09;是 Java 中最常…

输电线路可视化监拍装置硬件解决方案

老旧输电线路可视化监控装置 随着我国人口的增长&#xff0c;电力设施的规模也变得越发庞大&#xff0c;人工运检的负担也越来越沉重&#xff0c;而且巡检的时效性也是痛点&#xff0c;于是电网提出智慧可视化管理通道运检的方案&#xff0c;线路在线监测装置成为其基础&#x…

前端开发环境部署问题(高级程序员必备)

很多开发者到了一家新公司&#xff0c;公司发了一台新电脑&#xff0c;对环境安装比较困惑。今天带大家还原&#xff0c;拿到公司电脑&#xff0c;如何安装你需要的各种环境。 一、node安装 官网下载地址&#xff1a; http://nodejs.cn/download/ 根据自己需要下载对应的版…

电脑多久重装一次系统比较好

在长时间使用电脑后&#xff0c;一些用户可能会考虑重装系统来提升性能和稳定性。然而&#xff0c;电脑重装系统的频率是一个有争议的问题。本文将探讨电脑重装系统的最佳频率&#xff0c;以帮助您做出明智的决策。 工具/原料&#xff1a; 系统版本&#xff1a;win7旗舰版 品…

如何使用Leangoo领歌敏捷工具管理Sprint Backlog

什么是Sprint Backlog&#xff1f; Sprint Backlog是Scrum的主要工件之一。在Scrum中&#xff0c;团队按照迭代的方式工作&#xff0c;每个迭代称为一个Sprint。在Sprint开始之前&#xff0c;PO会准备好产品Backlog&#xff0c;准备好的产品Backlog应该是经过梳理、估算和优先…

编译原理期末速成-自上而下分析、消除文法的左递归问题

文章目录 自上而下分析面临的问题文法左递归问题回溯问题 构造不带回溯的自上而下分析算法消除文法的左递归 自上而下分析 自上而下就是从文法的开始符号出发&#xff0c;向下推导&#xff0c;推出句子。 面临的问题 文法左递归问题 左递归在语法分析过程中可能会导致无限循环…

测试员,自己都不上心,就不要抱怨别人对你冷眼旁观

昨日表哥恳请帮他的学生投递一下开发岗的简历&#xff0c;举手之劳&#xff0c;这忙必须得帮。但当发来学生的简历后&#xff0c;我吐槽了“这简历平平无奇&#xff0c;没有任何亮点&#xff0c;如何令人另眼相看&#xff1f;”表哥说&#xff0c;学生经历不多&#xff0c;总不…

Echarts 显示指定区域内容 + 不允许控制缩放

需求 在开发中&#xff0c;遇到如下一个需求&#xff0c;要展示指定区域的图形&#xff0c;并控制其不允许缩放 分析 第一想到的就是控件 dataZoom 中设置指定展示的区域&#xff0c;总结如下 解决 示例 源码 option: {xAxis: {type: "category",data: ["A…

医疗设备都在用哪些晶振?

医疗设备是指用于医疗诊断、治疗、监测等方面的各种设备。随着科技的不断发展&#xff0c;医疗设备的功能不断增强&#xff0c;精度和稳定性也得到了大幅提升。在这些医疗设备中&#xff0c;晶振是非常重要的元件之一。本文将介绍医疗设备中常用的晶振类型及其特点。 一、晶振…

Apache Kafka学习

目录 一、简介 1.概念&#xff1a; 2.kafka四大API&#xff1a; 3.Kafka消费模式 4.Kafka的基础架构 5.kafka文件存储方式 二、特性 三、优点 1.解耦 2.异步处理 3.流量削峰 4.数据持久化 5.顺序保证 6.可恢复性 四、名词解释 五、QA Q:如何保证数据高可靠、不…

RK3288 Android8.1添加lvds以及gt9触摸屏(一)

我们公司的屏幕是分为两部分 1.lvds负责屏幕亮起&#xff0c;显示UI 2.gt9触摸屏负责触摸点击反馈操作 现在先说lvds如何配置 RK的LVDS屏调试&#xff0c;主要是配置正确LVDS的dts&#xff0c;配置正确基本都是可以点亮的 1 首先拿到LVDS屏厂商给的屏规格书&#xff0c;规格…

微信小程序开发大坑盘点

微信小程序开发大坑盘点 起因 前几天心血来潮&#xff0c;想给学校设计个一站式校园小程序&#xff0c;可以查询成绩&#xff0c;考试信息&#xff0c;课表之类的&#xff08;本来想法里是还想包括一些社交功能的&#xff0c;但这个因为资质问题暂且搁置了&#xff09;。其实…

原生态Ajax价绍与使用方法

目录 什么是Ajax&#xff1f; 什么是原生态Ajax&#xff1f; Ajax使用方法与步骤 步骤&#xff1a; 代码示例&#xff1a; 什么是Ajax&#xff1f; 当谈到Ajax&#xff08;Asynchronous JavaScript and XML&#xff09;时&#xff0c;我们指的是一种用于在网页上进行异步…

效率低?不灵活?别担心,试试低代码应用开发平台,一招搞定!

在日常办公中&#xff0c;你有没有遇到办公效率低下、表格制作不灵活等常见问题&#xff1f;在大数据时代&#xff0c;这样的问题在现代化办公环境中经常遇到&#xff0c;也成为了大众头疼问题之一。要想解决这个问题&#xff0c;可以了解低代码应用开发平台&#xff0c;它的灵…

【六一儿童节】九九乘法表

文章目录 前言循环嵌套循环结语 前言 非常感谢您点进来这篇特殊的文章&#xff0c;时间匆匆&#xff0c;不知不觉已来到了自己的第三十余个儿童节。 很开心&#xff0c;小时候节日在学校和小朋友们一起玩耍&#xff0c;后来长大了又和兄弟们一起疯。很幸运!现在还有公司带着我们…