当前位置: 首页 > news >正文

静态通讯录动态通讯录制作详解

    🍕在本期的博客我们来向大家介绍一下静态通讯录的书写以及怎样将我们的静态通讯录更改为动态的模式。

    🍔静态通讯录的创建

    🍕就像是我们之前进行的完整程序逻辑的书写一样我们同样创建三个文件,两个 .c 文件,一个 .h 文件。之后依次来进行详细逻辑的书写。我们依旧先来梳理一下我们静态通讯录程序的书写逻辑:

    🍕首先我们的通讯录是一个多元素的集合体:其中包括联系人姓名,联系人性别,联系人电话等信息。所以我们就需要定义一个结构题变量进而构建我们的结构体类型的集合。同时根据我们程序的需求来进行特定的操作。比如联系人的新增,删除,显示,以及按照姓名查找特定的联系人等操作。

    🍕我们通讯录的设计在主函数的test部分是和我们之前的程序的编写是相同的。都是利用do_while循环至少进行一次操作的实现之后在进行下一步操作的选择。我们通讯录的主体部分的编写依旧是我们的switch选择语句。这一部分比较简单,我们本次博客不再进行过多的讲解。

    其代码如下:

#include"contact.h"int main()
{int input = 0;do{meun();printf("请选择:");scanf("%d", &input);switch (input){case 1:break;case2:break;case 3:break;case4:break;case 5:break;case 6:break;case 0:break;default:break;}} while (input);return 0;
}

    🍕其大体结构就像是我们上面显示的那样,我们把程序的主体部分空出来以便于之后在相应的部分当中加入我们对应的功能。(和我们之前编写的函数相同 ,我们的主要的大部分函数在contact.c中进行实现,在我们的contact.h中进行声明,包括我们的头文件的声明)。

    🍕之后开始我们通讯录的正式的编写。我们在主函数中定义一个结构体,在其中声明通讯录单个对象的数据类型。就像我们正常情况下的认知一样。一般的联系人的信息有姓名,年龄,电话,性别等信息。在这里为了简便我们变量的定义,所以我们此次只定义四个变量。如果自己在编写程序的时候可以自行添加。创建之后的效果如下:

//结构体的声明
typedef struct PeoInfo
{char name[20];char sex[5];int age;char phone[12];
}PeoInfo;

    🍕当然,在我们的通讯录当中不可能只有一个联系人,因此我们还需要定义一个结构体的数组data用来存放我们不同的联系人的信息。同时值得我们注意的是,在这个数组中元素的打印个数应该怎么控制呢?我们总不可能每一次都将我们空的联系人列表也打印出来吧?所以在这里我们重新定义一个新的整形变量sz,用来管理每次通讯录打印联系人的个数。为了便于我们进行程序的编写,我们创建一个新的结构体变量来管理这两者数据。这两个数据是我们在之后程序的编写当中使用最多的部分。代码如下:

//结构体的信息
typedef struct contact
{PeoInfo data[100];int sz;
}contact;

    🍕下一个步骤就来到了我们对于通讯录的初始化了,相信有一定的计算机基础的伙伴们都应该知道当我们对数据只进行定义却不进行初始化的时候数据中放置的内容是一个随机值,因此为了避免一些不必要的麻烦,我们需要先对定义好的结构体数组和我们的整形sz元素进行初始化。我们凭借函数封装的思想我们同样可以将我们的初始化操作封装成一个函数。注意为了保证我们数组中的全部的数据都是0,在这里我们可以使用我们之前学习过的memset函数进行单个字节逐个初始化。初始化函数代码如下:

void Initcontact(contact* pa)
{pa->sz = 0;memset(pa->data, 0, sizeof(pa->data));
}

     🍕在我们初始化好结构体变量之后我们我们就可以进行我们主要函数部分的完善了。首先我们来完善第一部分添加联系人的add函数。

    🍔添加联系人函数——add函数

    🍕首先我们应该想到的就是我们可能会出现的那些特殊情况了,就比如当我们的通讯录满的时候我们就无法再继续向我们的通讯录中添加数据了。因此我们需要在实现我们添加联系人的操作之前,先进行一次判断,判断这个通讯录是否为满,如果为满的话我们就直接退出,不需要再进行下一步的操作。通讯录不满的时候我们在进行相关数据的添加。我们创建的函数因为需要对于结构体进行相应的改写,因此需要将我们函数的形参和实参类型都定义成结构体指针的类型。例如:   add(&con)。其中con为我们在主函数中创建的结构体的变量名。之后我们就可以利用指针继续相应的数据的改写操作。我们需要通过形参中的指针变量找到我们的结构体数组,再利用下标和点操作符进行相应数据的改写。其代码如下:

void add(contact* pa)
{if (pa->sz == 100){printf("通讯录已满,无法添加。\n");return;}//添加一个人的信息printf("请输入名字:");scanf("%s", pa->data[pa->sz].name);printf("请输入电话:");scanf("%s", pa->data[pa->sz].phone);printf("请输入性别:");scanf("%s", pa->data[pa->sz].sex);printf("请输入年龄:");scanf("%d", &(pa->data[pa->sz].age));pa->sz++;
}

    🍕当我们完成上述步骤的时候,我们的添加联系人的操作也就完成了。但是我们只是添加了联系人却并不能检测我们程序编写是否存在细小的错误,所以我们下一个步骤就是编写我们的show函数将我们添加之后的联系人的信息打印出来,便于我们检测程序的正确性。

    🍔联系人显示函数——show函数

    🍕 和我们add函数编写的原理大致相同,在这里我们为了和之前的函数参数的形式相照应,因此在这里我们我们同样向函数中传入结构体指针作为函数参数。在这一部分我们使用指针找到我们的结构体的数组在进行相应数据的打印。具体的代码如下:

void show(contact* pa)
{int i = 0;for (i = 0; i < pa->sz; i++){printf("%s\t%s\t%d\t%s\n", pa->data[i].name,pa->data[i].sex,pa->data[i].age,pa->data[i].phone);}
}

    🍕当我们的show函数编写完成之后就可以进行程序效果的检查了。只需要在我们主函数中的switch语句中填入相应的函数即可,程序运行的效果如下:

    🍕我们先选择1添加一系列的数据向我们的通讯录当中,之后选择5调用我们show函数,显示我们的通讯录的主要的数据对象。

    🍔联系人的查找——find函数

    🍕之后我们来完成我们的下一个函数。根据分析我们可以知道:当我们在调用del函数的时候我们需要先找到我们想要删除的联系人。如果我们想要修改我们的联系人的信息的时候我们同样需要先找到我们指定联系人的信息,所以我们只需要完成我们的find函数的时候,之后我们其他相应函数的编写只需要复用find函数的内容即可。find函数的编写其实也很简单,我们只需要遍历我们的结构体数组中的数据即可,只要我们查找的数据项相同的时候我们返回当前的下标,如果没有找到,就返回-1。便于我们后面的函数的使用。find函数的具体实现代码如下:

int find(contact* pa)
{if (pa->sz == 0){printf("通讯录为空,无法查找。\n");return -1;}int i = 0;char name[20];printf("请输入你想要查找的对象的名字:");scanf("%s", name);for (i = 0; i < pa->sz; i++){if (strcmp(pa->data[i].name, name) == 0){printf("%-5s\t%-5s\t%-5s\t%-13s\n","姓名","性别","年龄","电话");printf("%-5s\t%-5s\t%-5d\t%-13s\n", pa->data[i].name, pa->data[i].sex, pa->data[i].age, pa->data[i].phone);return i;}}printf("没有找到相应的联系人。\n");return -1;
}

    🍕在这里我们需要注意的同样是对于我们通讯录逻辑的构建,假如我们的结构体数组中的元素个数为0时,就无需检查元素对象,直接提示并退出即可,如果没有找到也进行提示并退出。那么我们find函数的主要内容也就实现完毕了。

    🍔联系人删除函数——del函数

   🍕接着编写我们的del函数,我们同样需要先判断通讯录是否为空,如果为空,直接提示并返回,之后我们服用我们find函数当中的代码,就可以直接查找到我们相应的元素,如果函数的返回值不为-1的时候就代表找到了相应的数据元素,之后我们就可以进行下一步的删除操作。如果返回值为-1,那么就代表没有找到我们数据的元素,所以我们需要提示并退出。在我们del函数的具体实现当中,我们只需要将元素一个一个从后向前移动即可。(使用后面的有效的数据遮盖住我们前面不需要使用的数据)最后对sz进行 -- 操作即可。值得我们在这一部分特别注意的是:我们要格外小心数组越界的问题,因此我们要将我们循环的结束条件 -1 以防止数组越界。这一部分的代码实现如下:

void del(contact* pa)
{if (pa->sz == 0){printf("通讯录为空,不能进行删除操作。");return;}char name[20];printf("请输入你想要删除联系人的名字:");scanf("%s", name);int ret = 0;int i = 0;for (i = 0; i < pa->sz; i++){if (strcmp(pa->data[i].name, name) == 0){printf("%-5s\t%-5s\t%-5s\t%-13s\n", "姓名", "性别", "年龄", "电话");printf("%-5s\t%-5s\t%-5d\t%-13s\n", pa->data[i].name, pa->data[i].sex, pa->data[i].age, pa->data[i].phone);ret = i;}}if (ret !=-1 ){//查找到相关的联系人并删除int i = 0;for (i = ret; i < pa->sz - 1; i++){pa->data[i] = pa->data[i + 1];}pa->sz--;printf("删除成功。\n");return;}else{printf("查无此人,请重新确认。\n");return;}
}

     🍕让程序运行起来,之后我们代码的运行效果就是如下:

    🍕可以看出我们程序运行的很顺利,最后一部分的功能就是我们的modify函数了。

    🍔联系人修改函数——modify函数

    🍕我们现在来完成我们程序的最后一部分:modify函数的实现。想要修改一个联系人我们第一步要做的就是查找到我们的联系人的信息。在这里我们同样复用我们的find函数的代码即可。之后对于新的修改的内容,我们可以利用我们find函数的返回值进行指定数据的改写。(返回值就是我们想要修改的数据的下标)直接进行复制改写即可。其实现的代码如下:

void modify(contact* pa)
{if (pa->sz == 0){printf("通讯录列表为空,无法进行修改。\n");return;}char name[20];printf("请输入你想要修改的联系人的姓名:");scanf("%s", name);int ret = 0;int i = 0;for (i = 0; i < pa->sz; i++){if (strcmp(pa->data[i].name, name) == 0){printf("%-5s\t%-5s\t%-5s\t%-13s\n", "姓名", "性别", "年龄", "电话");printf("%-5s\t%-5s\t%-5d\t%-13s\n", pa->data[i].name, pa->data[i].sex, pa->data[i].age, pa->data[i].phone);ret = i;}}if (ret != -1){//进行相关的修改操作printf("请输入新的名字:");scanf("%s", pa->data[ret].name);printf("请输入新的电话:");scanf("%s", pa->data[ret].phone);printf("请输入新的性别:");scanf("%s", pa->data[ret].sex);printf("请输入新的年龄:");scanf("%d", &(pa->data[ret].age));printf("修改成功。\n");return;}else{printf("查无此人,请重新确认。\n");return;}
}

    🍕我们让我们的程序运行起来进行检验,运行效果如下:

    🍕可以发现一切正常,那么到此为止,我们的静态通讯录的程序的编写也就完全结束了。下面我们将静态通讯录改写成动态通讯录。

    🍔动态通讯录 

    🍕 静态通讯录和动态通讯录的最大的差别就是静态通讯录只能再我们创建结构体数组的时候创建,一旦数据数量满的时候我们就不能再向其中输入数据。但是我们动态通讯录的编写就不需要考虑这个问题,动态通讯录会自动适应我们的数据的数量,当我们的位置不够的时候会自动开辟我们新的空间。让我们的通讯录变得更加的灵活。在这里我们使用calloc函数和realloc函数实现我们的动态的通讯录。

    🍕首先来梳理一下我们动态通讯录中需要完善的程序的功能,我们需要使用calloc函数开辟一个新的堆区的动态空间,之后再使用realloc函数调整增加我们的函数。

    🍕第一步我们需要调整我们的结构体的定义的内容。还记得我们最初对于数据数组的存储是使用一个定态数组,我们在这里将我们的定长数组修改为一个指针便于接收我们后面传入的动态开辟好的指针。

    🍕之后我们还需要修改初始化我们通讯录的函数。在这一部分我们需要修改的内容较多。首先我们再这一部分需要使用calloc函数开辟一个动态空间,(这里使用calloc函数不适用malloc函数的原因是calloc函数会自动初始化为0,便于节省我们的步骤。)在我们开辟好空间之后可以先创立一个指针进行接收,判断不为空指针之后在进行对结构体指针进行赋值。最后再将我们的sz的数值初始化为0。

    🍕在这里我们需要考虑的是我们在使用动态开辟的空间的时候肯定需要考虑并避免越界的问题,所以我们需要新创建一个capicity变量用于记录我们当前通讯录的容量,如果通讯录的容量使用完毕之后需要考虑我们的增容的问题。我们创建初始化通讯录的代码如下:

//动态通讯录
void Initcontact(contact* pa)
{//初始化通讯录PeoInfo*ptr =(PeoInfo*)calloc(3, sizeof(PeoInfo));if (ptr != NULL){pa->data = ptr;}pa->sz = 0;pa->capicity = 3;
}

    🍕之后我们需要在我们之前创建好的add函数中假如我们的整容的判断,如果当我们的容量和我们的数据的数量相等的时候就进行增容。

    🍕我们可以重新定义一个check_capicity函数,并在适当的时候进行通讯录的扩容(使用realloc重新调整空间的大小,如果我们容量增加之后就可以给用户提示:增容成功。)增容部分的代码如下:

//检查增容的函数
void check_capicity(contact* pa)
{if (pa->sz < pa->capicity){return;}else{//通讯录已满,需要增容PeoInfo* ptr = realloc(pa->data, (pa->capicity + 2) * sizeof(PeoInfo));if (ptr != NULL){pa->data == ptr;pa->capicity = pa->capicity + 2;printf("增容成功。\n");return;}else{printf("增容失败。\n");return;}}
}//动态通讯录增加函数
void add(contact* pa)
{//检查是否需要增容check_capicity(pa);//添加一个人的信息printf("请输入名字:");scanf("%s", pa->data[pa->sz].name);printf("请输入电话:");scanf("%s", pa->data[pa->sz].phone);printf("请输入性别:");scanf("%s", pa->data[pa->sz].sex);printf("请输入年龄:");scanf("%d", &(pa->data[pa->sz].age));pa->sz++;
}

    🍕当我们编写完增容函数之后会发现其他的内容都并没有改变,那么我们动态修改通讯录的最后一步就是创建一个destory函数了。因为我们向堆区动态开辟一个内存使用完之后,肯定要去释放我们开辟好的内存。否则就可能会造成内存泄漏的问题。

    🍕对于我们的destory函数的编写其实很简单,只需要将我们接收指针的变量free(释放)掉,之后就可以了。之后将我们的指针置为空指针。将我们通讯录的容量修改为0,变将我们的sz修改为0。之后我们通讯录的修改也就结束了。

    🍔文件保存

    🍕当我们的动态通讯录书写完毕之后我们就会发现,虽然一切功能看似正常,但是唯一的不足就是不能保存。所以为了让我们的代码更加的完善我们增加一个函数的保存的函数。

    🍕和我们正常的文件保存函数相同的是,我们通讯录的文件保存是打开一个文件然后以文本或者二进制的形式写入文件当中。首先我们需要使用fopen函数打开一个文件,之后在关闭文件之前就可以向文件中写入我们想要写入的数据。在这里我们以二进制的形式向文件中写入数据。(使用fwrite函数)等我们写完文件之后就可以关闭文件并将我们打开文件的接受指针置为空即可。文件保存部分的函数如下:

//保存通讯录函数
void SaveContact(contact* pc)
{//写数据//1, 打开文件FILE* pf = fopen("contact.txt", "wb");if (NULL == pf){perror("SaveContact");}else{//写数据int i = 0;for (i = 0; i < pc->sz; i++){fwrite(pc->data + i, sizeof(PeoInfo), 1, pf);}fclose(pf);pf = NULL;printf("保存成功\n");}
}

    🍕等我们的数据全部写入内存之后我们需要考虑的是我们只是增添了向文件中写入数据的函数,但是我们每次运行程序的时候还得从文件当中读取数据,那么我们就需要重新创建一个文件读取函数。我们只需要格外注意一点:那就是我们必须在向内存中写入数据的时候必须考虑到容量的问题,当我们的容量满的时候就需要考虑增容的情况,这个时候我们就需要调用我们上面写好的增容的函数。文件读取函数部分的代码如下:

void LoadContact(contact* pc)
{//读数据//1. 打开文件FILE* pf = fopen("contact.txt", "rb");if (pf == NULL){perror("LoadContact");}else{//2. 读数据PeoInfo tmp = { 0 };int i = 0;while (fread(&tmp, sizeof(PeoInfo), 1, pf)){//增容check_capicity(pc);pc->data[i] = tmp;pc->sz++;i++;}fclose(pf);pf = NULL;}
}

    🍕以上我们的通讯录的程序也就全部完成了。下面是有关通讯录的全部代码,可以自行参考。那么本次博客的内容也就到此结束了,感谢您的观看与支持。

//contact.c文件
#define _CRT_SECURE_NO_WARNINGS
#include"contact.h"void meun(void)
{printf("***********************************\n");printf("***   1.add       2.find        ***\n");printf("***   3.del       4.modify      ***\n");printf("***   5.show      0.exit        ***\n");printf("***********************************\n");
}//静态通讯录
//void Initcontact(contact* pa)
//{
//	pa->sz = 0;
//	memset(pa->data, 0, sizeof(pa->data));
//}//动态通讯录
void Initcontact(contact* pa)
{//初始化通讯录PeoInfo*ptr =(PeoInfo*)calloc(3, sizeof(PeoInfo));if (ptr != NULL){pa->data = ptr;}pa->sz = 0;pa->capicity = 3;
}//静态通讯录增加函数
//void add(contact* pa)
//{
//	if (pa->sz == 100)
//	{
//		printf("通讯录已满,无法添加。\n");
//		return;
//	}
//	//添加一个人的信息
//	printf("请输入名字:");
//	scanf("%s", pa->data[pa->sz].name);
//	printf("请输入电话:");
//	scanf("%s", pa->data[pa->sz].phone);
//	printf("请输入性别:");
//	scanf("%s", pa->data[pa->sz].sex);
//	printf("请输入年龄:");
//	scanf("%d", &(pa->data[pa->sz].age));
//	pa->sz++;
//}//检查增容的函数
void check_capicity(contact* pa)
{if (pa->sz < pa->capicity){return;}else{//通讯录已满,需要增容PeoInfo* ptr = realloc(pa->data, (pa->capicity + 2) * sizeof(PeoInfo));if (ptr != NULL){pa->data = ptr;pa->capicity = pa->capicity + 2;printf("增容成功。\n");return;}else{printf("增容失败。\n");return;}}
}//动态通讯录增加函数
void add(contact* pa)
{//检查是否需要增容check_capicity(pa);//添加一个人的信息printf("请输入名字:");scanf("%s", pa->data[pa->sz].name);printf("请输入电话:");scanf("%s", pa->data[pa->sz].phone);printf("请输入性别:");scanf("%s", pa->data[pa->sz].sex);printf("请输入年龄:");scanf("%d", &(pa->data[pa->sz].age));pa->sz++;
}void show(contact* pa)
{int i = 0;for (i = 0; i < pa->sz; i++){printf("%s\t%s\t%d\t%s\n", pa->data[i].name,pa->data[i].sex,pa->data[i].age,pa->data[i].phone);}
}int find(contact* pa)
{if (pa->sz == 0){printf("通讯录为空,无法查找。\n");return -1;}int i = 0;char name[20];printf("请输入你想要查找的对象的名字:");scanf("%s", name);for (i = 0; i < pa->sz; i++){if (strcmp(pa->data[i].name, name) == 0){printf("%-5s\t%-5s\t%-5s\t%-13s\n","姓名","性别","年龄","电话");printf("%-5s\t%-5s\t%-5d\t%-13s\n", pa->data[i].name, pa->data[i].sex, pa->data[i].age, pa->data[i].phone);return i;}}printf("没有找到相应的联系人。\n");return -1;
}//通讯录销毁函数
void Destory(contact* pa)
{free(pa->data);pa->data = NULL;pa->capicity = 0;pa->sz = 0;return;
}void del(contact* pa)
{if (pa->sz == 0){printf("通讯录为空,不能进行删除操作。");return;}char name[20];printf("请输入你想要删除联系人的名字:");scanf("%s", name);int ret = 0;int i = 0;for (i = 0; i < pa->sz; i++){if (strcmp(pa->data[i].name, name) == 0){printf("%-5s\t%-5s\t%-5s\t%-13s\n", "姓名", "性别", "年龄", "电话");printf("%-5s\t%-5s\t%-5d\t%-13s\n", pa->data[i].name, pa->data[i].sex, pa->data[i].age, pa->data[i].phone);ret = i;}}if (ret !=-1 ){//查找到相关的联系人并删除int i = 0;for (i = ret; i < pa->sz - 1; i++){pa->data[i] = pa->data[i + 1];}pa->sz--;printf("删除成功。\n");return;}else{printf("查无此人,请重新确认。\n");return;}
}void modify(contact* pa)
{if (pa->sz == 0){printf("通讯录列表为空,无法进行修改。\n");return;}char name[20];printf("请输入你想要修改的联系人的姓名:");scanf("%s", name);int ret = 0;int i = 0;for (i = 0; i < pa->sz; i++){if (strcmp(pa->data[i].name, name) == 0){printf("%-5s\t%-5s\t%-5s\t%-13s\n", "姓名", "性别", "年龄", "电话");printf("%-5s\t%-5s\t%-5d\t%-13s\n", pa->data[i].name, pa->data[i].sex, pa->data[i].age, pa->data[i].phone);ret = i;}}if (ret != -1){//进行相关的修改操作printf("请输入新的名字:");scanf("%s", pa->data[ret].name);printf("请输入新的电话:");scanf("%s", pa->data[ret].phone);printf("请输入新的性别:");scanf("%s", pa->data[ret].sex);printf("请输入新的年龄:");scanf("%d", &(pa->data[ret].age));printf("修改成功。\n");return;}else{printf("查无此人,请重新确认。\n");return;}
}//保存通讯录函数
void SaveContact(contact* pc)
{//写数据//1, 打开文件FILE* pf = fopen("contact.txt", "wb");if (NULL == pf){perror("SaveContact");}else{//写数据int i = 0;for (i = 0; i < pc->sz; i++){fwrite(pc->data + i, sizeof(PeoInfo), 1, pf);}fclose(pf);pf = NULL;printf("保存成功\n");}
}void LoadContact(contact* pc)
{//读数据//1. 打开文件FILE* pf = fopen("contact.txt", "rb");if (pf == NULL){perror("LoadContact");}else{//2. 读数据PeoInfo tmp = { 0 };int i = 0;while (fread(&tmp, sizeof(PeoInfo), 1, pf)){//增容check_capicity(pc);pc->data[i] = tmp;pc->sz++;i++;}fclose(pf);pf = NULL;}
}//contact.h文件
#define _CRT_SECURE_NO_WARNINGS#include<stdio.h>
#include<string.h>
#include<stdlib.h>//菜单函数
void meun(void);//结构体的声明
typedef struct PeoInfo
{char name[20];char sex[5];int age;char phone[12];
}PeoInfo;//结构体的信息
typedef struct contact
{PeoInfo* data;int sz;int capicity;
}contact;//初始化通讯录的函数
void Initcontact(contact* pa);//显示我们制作好的通讯录
void show(contact* pa);//查找联系人的函数
int find(contact* pa);//删除联系人的函数
void del(contact* pa);//修改联系人信息的函数
void modify(contact* pa);//通讯录销毁函数
void Destory(contact*pa);//退出时保存通讯录的函数
void SaveContact(contact* pc);//加载通讯录信息到通讯录
void LoadContact(contact* pc);//test.c文件
#define _CRT_SECURE_NO_WARNINGS
#include"contact.h"int main()
{//创建通讯录contact con;int input = 0;//初始化通讯录Initcontact(&con);LoadContact(&con);do{meun();printf("请选择:");scanf("%d", &input);switch (input){case 1:add(&con);break;case 2:find(&con);break;case 3:del(&con);break;case 4:modify(&con);break;case 5:show(&con);break;case 0:SaveContact(&con);printf("退出通讯录。\n");Destory(&con);break;default:printf("输入错误请重新输入。\n");break;}} while (input);return 0;
}

相关文章:

静态通讯录动态通讯录制作详解

&#x1f355;在本期的博客我们来向大家介绍一下静态通讯录的书写以及怎样将我们的静态通讯录更改为动态的模式。 &#x1f354;静态通讯录的创建 &#x1f355;就像是我们之前进行的完整程序逻辑的书写一样我们同样创建三个文件&#xff0c;两个 .c 文件&#xff0c;一个 .h 文…...

2023最新最详细【接口测试总结】

序章 ​ 说起接口测试&#xff0c;网上有很多例子&#xff0c;但是当初做为新手的我来说&#xff0c;看了不不知道他们说的什么&#xff0c;觉得接口测试&#xff0c;好高大上。认为学会了接口测试就能屌丝逆袭&#xff0c;走上人生巅峰&#xff0c;迎娶白富美。因此学了点开发…...

【java基础】Stream流的各种操作

文章目录基本介绍流的创建流的各种常见操作forEach方法filter方法map方法peek方法flatMap方法limit和skip方法distinct方法sorted方法收集结果收集为数组&#xff08;toArray&#xff09;收集为集合&#xff08;collect&#xff09;收集为Map关于流的一些说明&#xff08;终结操…...

【Python练习】序列结构

目录 一、实验目标 二、实验内容...

CDN加速缓存的定义与作用

一、CDN的含义CDN的全称是Content Delivery Network&#xff0c;即内容分发网络。CDN是在原有互联网的基础上再构建虚拟分发网络&#xff0c;利用部署在各地的边缘节点服务器&#xff0c;充分发挥其负载均衡、内容分发智能调度等功能&#xff0c;让用户能够就地拉取数据&#x…...

Java并发高频面试题

分享50道Java并发高频面试题。 线程池 线程池&#xff1a;一个管理线程的池子。 为什么平时都是使用线程池创建线程&#xff0c;直接new一个线程不好吗&#xff1f; 嗯&#xff0c;手动创建线程有两个缺点 不受控风险频繁创建开销大 为什么不受控&#xff1f; 系统资源有…...

CVPR 2023 | 旷视研究院入选论文亮点解读

近日&#xff0c;CVPR 2023 论文接收结果出炉。近年来&#xff0c;CVPR 的投稿数量持续增加&#xff0c;今年收到有效投稿 9155 篇&#xff0c;和 CVPR 2022 相比增加 12%&#xff0c;创历史新高。最终&#xff0c;大会收录论文 2360 篇&#xff0c;接收率为 25.78 %。本次&…...

Vue3 学习总结补充(一)

文章目录1、Vue3中为什么修改变量的值后&#xff0c;视图不更新&#xff1f;2、使用 ref 还是 reactive&#xff1f;3、reactive 为什么会有响应性连接丢失情况&#xff1f;4、watch的不同使用方法5、watchEffect和 watch 的区别区别1&#xff1a;数据源的区别区别2&#xff1a…...

使用ChatGPT 开放的 API 接口可以开发哪些自研工具?

使用ChatGPT开放的API接口,可以开发多种自研工具,例如: 智能聊天机器人:可以使用ChatGPT提供的语言生成能力,构建一个智能聊天机器人,能够根据用户的输入自动回复,完成自然语言交互。 文本生成工具:可以使用ChatGPT的文本生成能力,开发一个文本生成工具,例如自动生…...

I2C和SPI总线以及通信

通讯属性 概括 Serial/parallel 串行/并行Synchronous/asynchronous 同步/异步Point-to-point / bus 点对点 总线Half-duplex/full-duplex 半双工/全双工Master-slave/ equal partners 主从/对等single-ending / differential 单端/差分 点对点和总线 点对点通讯 只有两个通…...

Spring八股文

Bean的生命周期 1.通过反射生成对象 2.填充Bean的属性 3.调用aware接口的invokeAwareMethod方法&#xff0c;对BeanName、BeanFactory、BeanClassLoader对象的属性设值 4.调用BeanPostProcessor的前置处理方法&#xff0c;其中使用较多的是ApplicationContextPostProcessor…...

20 k8sMetric 简介

一. Metric 简介metrics-server 可实现 Kubernetes 的 Resource Metrics API&#xff08;metrics.k8s.io&#xff09;&#xff0c;通过此 API 可以查询 Pod 与 Node 的部分监控指标&#xff0c;Pod 的监控指标用于 HPA、VPA 与 kubectl top pods -n ns 命令&#xff0c;而 Node…...

面试问了解Linux内存管理吗?10张图给你安排的明明白白

linux内存管理&#xff0c;内存管理好像离我们很远&#xff0c;但这个知识点虽然冷门&#xff08;估计很多人学完根本就没机会用上&#xff09;但绝对是基础中的基础&#xff0c;这就像武侠中的内功修炼&#xff0c;学完之后看不到立竿见影的效果&#xff0c;但对你日后的开发工…...

【C++】内联函数inline

文章目录概念使用特性原理概念 C中内联函数的出现解决了C语言宏函数的不足&#xff0c;类似于宏展开&#xff0c;这种在函数调用处直接嵌入函数体的函数称为内联函数&#xff0c;又称内嵌函数或内置函数。 以inline修饰的函数叫做内联函数&#xff0c;编译时C编译器会在调用内…...

C++演讲比赛流程管理系统_黑马

任务 学校演讲比赛&#xff0c;12人&#xff0c;两轮&#xff0c;第一轮淘汰赛&#xff0c;第二轮决赛 选手编号 [ 10001 - 10012 ] 分组比赛 每组6人 10个评委 去除最高分 最低分&#xff0c;求平均分 为该轮成绩 每组淘汰后三名&#xff0c;前三名晋级决赛 决赛 前三名胜出 …...

谈谈低代码的安全问题,一文全给你解决喽

低代码是一种软件开发方法&#xff0c;通过使用图形化用户界面和可视化建模工具&#xff0c;以及自动生成代码的技术&#xff0c;使得开发人员可以更快速地构建和发布应用程序。 作为近些年软件开发市场热门之一&#xff0c;市面上也涌现了许多低代码产品&#xff0c;诸如简道云…...

[数据结构]二叉树OJ(leetcode)

目录 二叉树OJ(leetcode)训练习题&#xff1a;&#xff1a; 1.单值二叉树 2.检查两棵树是否相同 3.二叉树的前序遍历 4.另一棵树的子树 5.二叉树的构建及遍历 6.二叉树的销毁 7.判断二叉树是否是完全二叉树 二叉树OJ(leetcode)训练习题&#xff1a;&#xff1a; 1.单值二叉…...

flutter 输入时插入分隔符

每四位插入一个分隔符import package:flutter/services.dart;class DividerInputFormatter extends TextInputFormatter {final int rear; //第一个分割位数,后面分割位,,数final String pattern; //分割符DividerInputFormatter({this.rear 4, this.pattern });overrideTex…...

静态版通讯录——“C”

各位CSDN的uu你们好呀&#xff0c;之前小雅兰学过了一些结构体、枚举、联合的知识&#xff0c;现在&#xff0c;小雅兰把这些知识实践一下&#xff0c;那么&#xff0c;就让我们进入通讯录的世界吧 实现一个通讯录&#xff1a; 可以存放100个人的信息每个人的信息&#xff1a;名…...

前端基础开发环境搭建工具等

一、基本开发环境&#xff08;软件&#xff09;安装1、Vscode&#xff08;代码编辑器&#xff09;官网下载网址&#xff1a;https://code.visualstudio.com/2、nvm&#xff08;node多版本管理器&#xff0c;每个node版本都有对应的npm版本&#xff09;安装包下载地址&#xff1…...

graph-autofusion:算子自动融合框架,让模型性能提升30%

前言 算子融合就像把多个快递包裹合并成一个&#xff0c;减少送货次数。 你有没有想过&#xff0c;为什么模型推理时&#xff0c;每个算子都要单独读写HBM&#xff08;High Bandwidth Memory&#xff09;&#xff1f;明明LayerNorm后面紧跟Add&#xff0c;为什么要分开算&#…...

10M参数也能跑ARC与数独,Bengio团队押注「多轨迹推理」

10M 参数跑到数独 97%&#xff0c;GRAM 把递归推理改成多轨迹采样。 10M 参数&#xff0c;在大模型时代显得有些微不足道。 但 Yoshua Bengio 团队与 KAIST、Mila、NYU 研究人员提出的 GRAM&#xff0c;用这个量级的模型跑出了几组值得注意的结果。 在 Sudoku-Extreme 上准确率…...

【顶级EI复现】考虑用户行为基于扩散模型的电动汽车充电场景生成( Python + PyTorch代码实现)

&#x1f4a5;&#x1f4a5;&#x1f49e;&#x1f49e;欢迎来到本博客❤️❤️&#x1f4a5;&#x1f4a5; &#x1f3c6;博主优势&#xff1a;&#x1f31e;&#x1f31e;&#x1f31e;博客内容尽量做到思维缜密&#xff0c;逻辑清晰&#xff0c;为了方便读者。 &#x1f381…...

测试工程师如何进行测试计划制定?这5个步骤让你的计划更合理

对于软件测试从业者而言&#xff0c;一份合理可行的测试计划是项目测试工作的核心纲领&#xff0c;它不仅决定了测试活动的范围、方向与资源分配&#xff0c;更直接影响着项目的交付质量与进度管控。很多初级测试工程师常常将测试计划等同于测试时间列表&#xff0c;要么写得过…...

如何在跨平台场景下实现高效远程桌面控制?BilldDesk Pro的现代化解决方案

如何在跨平台场景下实现高效远程桌面控制&#xff1f;BilldDesk Pro的现代化解决方案 【免费下载链接】billd-desk 基于Vue3 WebRTC Nodejs Flutter搭建的远程桌面控制、游戏串流 项目地址: https://gitcode.com/gh_mirrors/bi/billd-desk 在远程办公和技术支持日益普…...

【ChatGPT】锂电卷绕机深度拆解、信息图、爆炸图、C++代码框架

深度拆解信息图...

可控硅调光原理与舞台照明系统设计实战:以LTH16-08为例

1. 项目概述&#xff1a;舞台照明系统与可控硅的深度绑定在舞台、演播厅、剧场这些光影变幻的现场&#xff0c;每一束光的明暗、每一次色彩的渐变&#xff0c;背后都有一套精密、可靠且响应迅速的调光系统在支撑。从业十多年&#xff0c;我调试过无数灯光设备&#xff0c;深知其…...

Python数据库迁移实战:从SQLAlchemy到Alembic的完整指南

Python数据库迁移实战&#xff1a;从SQLAlchemy到Alembic的完整指南 引言 数据库迁移是后端开发中不可或缺的一部分。作为从Python转向Rust的后端开发者&#xff0c;我发现Python的数据库迁移工具非常成熟&#xff0c;尤其是Alembic配合SQLAlchemy的组合。本文将从实战角度出发…...

RabbitMQ 入门与安装

RabbitMQ 入门与安装&#xff1a;从 MQ 概念到环境搭建 一、开篇&#xff1a;学习 RabbitMQ 前需要准备什么 RabbitMQ 属于消息中间件&#xff0c;是 Java 后端开发中非常常见的一类基础组件。学习它之前&#xff0c;最好已经具备以下基础&#xff1a; 具备一定 Java 基础&…...

React 从入门到生产(五):状态管理选型

创作者&#xff1a; Yardon | GitHub&#xff1a; github.com/YardonYan | 版本&#xff1a; v1.0 什么时候需要状态管理 先泼一盆冷水&#xff1a;大多数 React 应用不需要 Redux。 这句话不是我说的&#xff0c;是 Redux 的作者 Dan Abramov 本人说的。他在 2020 年就公…...