C语言动态内存管理与文件操作:打造高效通讯录

news2024/11/14 21:23:27

本篇博客会讲解如何使用C语言实现一个通讯录。实现通讯录的过程中,会大量用到C语言的知识点,包括但不限于:函数、自定义类型、指针、动态内存管理、文件操作,这些知识点在我的其他博客中都有讲解过,欢迎大家阅读,这里就不进行系统的复习了。

先来梳理下需求:

  1. 通讯录能够存储的联系人的信息有:姓名、年龄、性别、电话、住址。
  2. 这个通讯录不能是“静态的”,而应该是“动态的”,也就是说,需要用到动态内存管理的知识。这是因为,静态的通讯录的容量是固定的,空间太大可能浪费,太小了又不够存。
  3. 由于当程序开始运行后,通讯录的数据是存储在内存中的,一旦程序运行结束,执行完main函数的return 0;后,空间就被操作系统回收了,相当于数据就丢了。为了能够实现“永久保存”的效果,我们要在程序退出前,把数据保存到文件中,这又涉及到文件操作的相关知识点。
  4. 类似顺序表这种数据结构的基本操作,通讯录要能做到:增删查改+排序+打印,即增加联系人、删除联系人、查找联系人、修改联系人、排序联系人、打印联系人等等。

下面我们开始吧!
在这里插入图片描述

准备工作

以下是菜单里的一些选项,声明成枚举类型是比较合适的。

// 菜单里的不同选项
enum Option
{
	EXIT,   // 退出
	ADD,    // 增加联系人
	DEL,    // 删除联系人
	SEARCH, // 查找联系人
	MODIFY, // 修改联系人
	SHOW,   // 显示联系人
	SORT    // 排序
};

由于联系人的姓名、性别、电话和住址都是字符串,要存储在字符数组中,最好先声明它们的容量。

// 各信息的存储容量
#define MAX_NAME 20 // 名字
#define MAX_SEX 5   // 性别
#define MAX_TELE 12 // 电话
#define MAX_ADDR 30 // 住址

我们后面在进行动态内存管理时,需要知道初始的容量和每次扩容的容量,也声明一下:

// 动态内存默认存储的数据
#define DEFAULT_SZ 3
// 若不够存,每次扩容的数量
#define INC_SZ 2

再声明一个结构体,表示一个人的信息,包括姓名、年龄、性别、电话、住址。

// 表示一个人的信息
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; // data指向了存放数据的空间
	int sz;        // 记录通讯录中的有效信息个数
	int capacity;  // 通讯录当前的容量
}Contact;

下面我们开始实现程序的主体逻辑。先从主函数写起,把主要的功能都封装成函数:

// 打印菜单
void menu()
{
	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");
}

int main()
{
	int input = 0; // 存储用户输入的数据
	Contact con; // 通讯录
	// 初始化通讯录
	// 加载文件的信息到通讯录中
	InitContact(&con);

	do
	{
		menu(); // 菜单
		printf("请选择:>");
		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: // 退出通讯录
			// 保存通讯录到文件中
			SaveContact(&con);
			// 销毁通讯录
			DestroyConact(&con);
			printf("退出通讯录\n");
			break;
		default:
			printf("选择错误\n");
			break;
		}
	} while (input);

	return 0;
}

初始化通讯录

先定义一个函数InitContact,它的作用是初始化通讯录。函数的参数是一个指向Contact结构体的指针。函数的具体实现如下:

  1. 函数的第一行使用assert宏检查指针是否有效,如果无效则程序会中止运行。
  2. 接下来,函数使用malloc函数开辟了一块内存空间,用于存储PeoInfo结构体数组。这个数组的大小是DEFAULT_SZ,即默认容量。如果开辟空间失败,则会输出错误信息并返回。
  3. 如果开辟空间成功,则将通讯录的大小sz和容量capacity都设置为DEFAULT_SZ,并调用LoadContact函数将文件中的信息加载到通讯录中。
void InitContact(Contact* pc)
{
	// 检查指针有效性
	assert(pc);

	// 先开辟默认的容量
	pc->data = (PeoInfo*)malloc(DEFAULT_SZ * sizeof(PeoInfo));
	// 检查开辟空间是否成功
	if (pc->data == NULL)
	{
		// 开辟空间失败
		printf("通讯录初始化失败:%s\n", strerror(errno));
		return;
	}
	// 开辟空间成功
	pc->sz = 0;
	pc->capacity = DEFAULT_SZ;

	//加载文件的信息到通讯录
	LoadContact(pc);
}

从文件中加载信息

再定义一个函数LoadContact,用于从文件中读取联系人信息并存储到内存中。函数的参数是一个指向Contact结构体的指针,表示要将读取到的联系人信息存储到哪个数据结构中。函数的具体实现如下:

  1. 首先使用assert函数检查传入的指针是否有效,如果无效则直接返回。
  2. 然后使用fopen函数打开名为"contact.dat"的二进制文件,如果打开失败则说明可能是第一次运行通讯录,没有数据文件,直接返回。
  3. 接着使用一个while循环,每次读取一个PeoInfo结构体大小的数据,即一个联系人的信息,存储到临时变量tmp中。
  4. 调用CheckCapacity函数检查当前动态数组的容量是否足够存储读取到的联系人信息,如果不够则进行扩容。
  5. 将读取到的联系人信息存储到动态数组中,即将tmp变量中的数据存储到data数组的末尾,并将sz变量加一。
  6. 循环结束后,关闭文件并将文件指针置为 NULL。
void LoadContact(Contact* pc)
{
	// 检查指针有效性
	assert(pc);

	// 打开文件
	FILE* pf = fopen("contact.dat", "rb");
	// 检查打开文件是否成功
	if (pf == NULL)
	{
		// 打开文件失败,可能是第一次运行通讯录,并没有数据文件
		//perror("LoadContact::fopen");
		return;
	}
	// 读文件
	PeoInfo tmp = { 0 }; // 存储读取到的数据
	// 每次读一个数据
	while (fread(&tmp, sizeof(PeoInfo), 1, pf))
	{
		// 检查容量,不够的话要扩容
		CheckCapacity(pc);
		// 存储从文件读取到的数据
		pc->data[pc->sz] = tmp;
		pc->sz++;
	}

	// 关闭文件
	fclose(pf);
	pf = NULL;
}

检查容量

再定义一个函数CheckCapacity,用于检查并扩容动态数组。函数的参数是一个指向Contact结构体的指针。函数的具体实现如下:

  1. 函数首先使用assert宏检查传入的指针是否有效。然后,它检查当前数组是否需要扩容。如果数组的大小已经等于容量,就需要扩容。
  2. 在需要扩容的情况下,函数使用realloc函数重新分配内存。realloc函数会尝试将原来分配的内存块扩大到指定的大小。如果扩容成功,realloc函数会返回一个指向新内存块的指针,否则返回NULL。
  3. 如果realloc函数返回 NULL,说明扩容失败,函数会输出错误信息并返回0。如果realloc函数返回非空指针,说明扩容成功,函数会更新数组的起始位置和容量,并输出扩容成功的信息。最后,函数返回1,表示扩容成功或者不需要扩容。
// 扩容失败,返回0
// 扩容成功,不需要扩容,返回1
static int CheckCapacity(Contact* pc)
{
	// 检查指针有效性
	assert(pc);

	// 检查是否需要扩容
	if (pc->sz == pc->capacity)
	{
		// 需要扩容
		PeoInfo* ptr = (PeoInfo*)realloc(pc->data, (pc->capacity + INC_SZ) * 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;
}

销毁通讯录

再定义一个函数DestroyContact,用来销毁通讯录。函数的参数是一个指向Contact结构体的指针。函数的具体实现如下:

  1. 先使用assert宏检查指针的有效性。
  2. 释放动态数组占用的内存空间。
  3. 把结构体的变量置空。
void DestroyConact(Contact* pc)
{
	// 检查指针有效性
	assert(pc);

	// 释放内存空间
	free(pc->data);
	pc->data = NULL;
	pc->capacity = 0;
	pc->sz = 0;
	printf("释放内存.....\n");
}

添加联系人

接着定义一个函数AddContact,参数仍然是一个指向Contact结构体的指针,实现向通讯录中添加联系人的功能。具体实现如下:

  1. 首先检查传入的指针是否有效,如果无效则使用assert宏触发断言,程序终止。
  2. 调用CheckCapacity函数检查通讯录是否需要扩容,如果需要则进行扩容操作。如果扩容失败,则输出提示信息并返回。
  3. 如果扩容成功,则提示用户输入联系人信息,包括名字、年龄、性别、电话和地址。这些信息将被存储在通讯录的data数组中,下标为 sz。
  4. 最后更新有效数据个数sz,并输出添加成功的提示信息。
void AddContact(Contact* pc)
{
	// 检查指针有效性
	assert(pc);

	// 扩容,同时检查是否成功
	if (0 == CheckCapacity(pc))
	{
		// 扩容失败
		printf("空间不够,扩容失败\n");
		return;
	}
	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");
	}
}

打印数据

接下来定义一个函数 ShowContact,用于打印联系人信息。函数接受一个指向Contact结构体的指针pc。具体实现如下:

  1. 函数首先使用assert宏检查指针pc是否有效,如果无效则程序会崩溃并输出错误信息。
  2. 接下来,函数使用printf函数打印联系人信息。首先打印表头,包括姓名、年龄、性别、电话和地址。然后使用循环遍历pc中的每个联系人,打印其姓名、年龄、性别、电话和地址。
  3. 在打印时使用了格式化字符串,其中 %s 表示字符串,%-10s 表示左对齐并占用 10 个字符的字符串,%-4d 表示左对齐并占用 4 个字符的整数,%-5s 表示左对齐并占用 5 个字符的字符串,%-12s 表示左对齐并占用 12 个字符的字符串,%-30s 表示左对齐并占用 30 个字符的字符串。
void ShowContact(const Contact* pc)
{
	// 检查指针有效性
	assert(pc);

	// 打印效果
	// 姓名      年龄      性别     电话      地址
	// zhangsan 20        男      123456    北京
	//
	// 打印标题
	printf("%-10s %-4s %-5s %-12s %-30s\n", "姓名", "年龄", "性别", "电话", "地址");
	// 打印数据
	for (int 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);
	}
}

删除联系人

接下来实现通讯录的删除联系人功能。具体实现如下:

FindByName函数:根据指定名字,在通讯录中查找联系人信息。函数参数为指向Contact结构体的指针和要查找的名字。函数返回值为查找到的联系人在通讯录中的下标,如果没找到则返回-1。

DelContact函数:删除通讯录中指定联系人。函数参数为指向Contact结构体的指针。函数实现如下:

  1. 检查指针有效性,如果为空则直接返回。
  2. 检查通讯录中是否还有数据,如果没有则输出提示信息并返回。
  3. 获取用户输入的要删除的联系人名字。
  4. 调用FindByName函数查找要删除的联系人在通讯录中的下标。
  5. 如果没找到,则输出提示信息并返回。
  6. 如果找到了,则使用memmove函数将该联系人后面的所有联系人向前移动一个位置,覆盖掉要删除的联系人。
  7. 更新通讯录中的有效数据个数。
  8. 输出删除成功的提示信息。
// 根据指定名字,查找联系人信息
static int FindByName(const Contact* pc, char name[])
{
	// 检查指针有效性
	assert(pc);
	assert(name);

	// 遍历数组
	for (int i = 0; i < pc->sz; ++i)
	{
		// 检查名字是否匹配
		if (0 == strcmp(pc->data[i].name, name))
		{
			// 找到了
			return i;
		}
	}
	// 没找到
	return -1;
}

void DelContact(Contact* pc)
{
	// 检查指针有效性
	assert(pc);

	// 检查是否还有数据
	if (pc->sz == 0)
	{
		// 通讯录已空
		printf("通讯录为空,无法删除\n");
		return;
	}

	char name[MAX_NAME] = { 0 }; // 存储用户输入的数据
	// 用户输入信息
	printf("输入要删除人的名字:>");
	scanf("%s", name);
	// 找到要删除的人的下标
	int pos = FindByName(pc, name);
	// 检查是否找到
	if (pos == -1)
	{
		// 没找到
		printf("要删除的人不存在\n");
		return;
	}
	// 删除pos位置上的数据
	// 挪动pos后面的数据,覆盖pos
	memmove(pc->data + pos, pc->data + pos + 1, sizeof(PeoInfo) * (pc->sz - pos - 1));
	// 更新有效数据个数
	pc->sz--;
	printf("删除成功\n");
}

查找联系人

接着实现在通讯录中根据姓名查找联系人的功能。具体实现如下:

  1. 函数名为SearchContact,接受一个指向Contact结构体的指针pc作为参数。
  2. 第一行代码使用assert宏检查指针pc是否有效,如果pc为 NULL,则程序会崩溃并输出错误信息。
  3. 定义一个char类型的数组name,长度为MAX_NAME,用于存储用户输入的要查找的人的名字。
  4. 使用printf函数输出提示信息,让用户输入要查找的人的名字。
  5. 使用scanf函数读取用户输入的名字,存储到name数组中。
  6. 调用FindByName函数,在通讯录中查找名字为name的联系人,返回值为该联系人在通讯录中的位置,如果没找到则返回-1。
  7. 判断FindByName函数的返回值,如果为-1,则说明没有找到要查找的联系人,使用printf函数输出提示信息,并直接返回。
  8. 如果FindByName函数的返回值不为-1,则说明找到了要查找的联系人,使用printf函数输出通讯录的标题行,包括姓名、年龄、性别、电话、地址等信息。
  9. 使用printf函数输出找到的联系人的具体信息,包括姓名、年龄、性别、电话、地址等信息,这些信息都存储在Contact结构体中的data数组中,通过pc指针访问。pos变量表示要查找的联系人在data数组中的位置。注意,这里使用了%-10s、%-4d等格式控制符,表示输出字符串时左对齐,并且占用固定的宽度,方便对齐。
void SearchContact(const Contact* pc)
{
	// 检查指针有效性
	assert(pc);

	char name[MAX_NAME] = { 0 }; // 存储用户输入的信息
	// 用户输入数据
	printf("请输入要查找人的名字:>");
	scanf("%s", name);
	// 查找
	int pos = FindByName(pc, name);
	// 检查是否找到
	if (pos == -1)
	{
		// 没找到
		printf("要查找的人不存在\n");
		return;
	}
	// 打印标题行
	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);
}

修改联系人

再下来实现一个函数,函数的参数仍然是一个指向通讯录结构体类型Contact的指针,用于修改通讯录中的联系人信息。具体实现如下:

  1. 使用assert宏函数检查指针有效性,如果指针为空,则程序会终止。
  2. 定义一个char类型数组name,用于存储用户输入的联系人名字。
  3. 使用printf函数提示用户输入要修改的联系人名字,并使用scanf函数读取用户输入的名字。
  4. 调用FindByName函数查找通讯录中是否存在该联系人,如果不存在则输出提示信息并返回。
  5. 如果存在该联系人,则使用scanf函数分别读取用户输入的联系人信息,包括名字、年龄、性别、电话和地址,并将这些信息存储到通讯录结构体中对应的位置。
  6. 最后使用printf函数输出修改成功的提示信息。
void ModifyContact(Contact* pc)
{
	// 检查指针有效性
	assert(pc);

	char name[MAX_NAME] = { 0 }; // 存储用户输入的信息
	// 用户输入数据
	printf("请输入要修改人的名字:>");
	scanf("%s", name);
	// 查找
	int pos = FindByName(pc, name);
	// 检查是否找到
	if (pos == -1)
	{
		// 没找到
		printf("要修改的人不存在\n");
		return;
	}
	// 修改
	// 用户输入信息
	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");
}

排序通讯录

接下来实现排序功能。具体实现如下:

  1. 定义了一个比较函数CmpByName,用于按照名字来排序联系人信息。该函数的参数为两个指向联系人信息结构体的指针p1和p2,返回值为两个名字字符串的比较结果。
  2. 在CmpByName函数中,首先使用assert宏检查指针p1和p2的有效性,确保程序不会因为无效指针而崩溃。然后使用strcmp函数比较两个联系人信息结构体中的名字字符串大小,返回比较结果。
  3. 定义了一个排序函数SortContact,用于对联系人信息进行排序。该函数的参数为一个指向联系人管理系统结构体的指针pc。
  4. 在SortContact函数中,首先使用assert宏检查指针pc的有效性,确保程序不会因为无效指针而崩溃。然后调用qsort函数对联系人信息进行排序,其中pc->data表示联系人信息数组的首地址,pc->sz表示联系人信息数组的大小,sizeof(PeoInfo)表示每个联系人信息结构体的大小,CmpByName表示排序函数。
  5. 最后输出排序成功的提示信息。
// 按照名字来排序
int CmpByName(const void* p1, const void* p2)
{
	// 检查指针有效性
	assert(p1 && p2);

	// 比较名字字符串大小
	return strcmp(((PeoInfo*)p1)->name, ((PeoInfo*)p2)->name);
}

void SortContact(Contact* pc)
{
	// 检查指针有效性
	assert(pc);

	// 根据名字来排序
	qsort(pc->data, pc->sz, sizeof(PeoInfo), CmpByName);
	printf("排序成功\n");
}

保存通讯录

最后实现将联系人信息保存到文件中的功能。具体实现如下:

  1. 函数定义:函数名为SaveContact,参数为一个指向Contact结构体的指针pc。
  2. 检查指针有效性:使用assert宏函数检查指针pc是否为NULL,如果是NULL则程序会直接终止。
  3. 打开文件:使用fopen函数打开名为"contact.dat"的文件,以二进制写入模式(“wb”)打开。返回值为一个指向FILE结构体的指针pf。
  4. 检查是否打开成功:使用if语句判断指针pf是否为NULL,如果是NULL则说明打开文件失败,使用perror函数输出错误信息并返回。
  5. 写数据:使用for循环遍历pc指向的Contact结构体中的所有联系人信息,使用fwrite函数将每个联系人信息写入文件中。其中,第一个参数为指向联系人信息的指针,第二个参数为每个联系人信息的大小,第三个参数为写入的数量,第四个参数为指向文件的指针pf。
  6. 关闭文件:使用fclose函数关闭文件,释放文件指针pf所占用的资源。将pf赋值为NULL,防止出现野指针。
  7. 输出保存成功信息:使用printf函数输出保存成功的信息。
void SaveContact(Contact* pc)
{
	// 检查指针有效性
	assert(pc);

	// 打开文件
	FILE* pf = fopen("contact.dat", "wb");
	// 检查是否打开成功
	if (pf == NULL)
	{
		// 打开文件失败
		perror("SaveContact::fopen");
		return;
	}
	// 写数据,一次写一个
	for (int i = 0; i < pc->sz; ++i)
	{
		fwrite(pc->data + i, sizeof(struct PeoInfo), 1, pf);
	}
	//关闭文件
	fclose(pf);
	pf = NULL;
	printf("保存成功...\n");
}

总结

  1. 动态内存管理一定要熟练掌握,包括使用malloc开辟空间,使用realloc扩容,使用free函数释放空间等。
  2. 常见的文件操作函数得会用,包括fopen,fclose以及文件的顺序读写和随机读写操作等。
  3. 顺序表的增删查改、排序打印等常规操作一定要掌握,通讯录的本质就是顺序表。

感谢大家的阅读!

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

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

相关文章

进程及进程管理

Linux基础 提示&#xff1a;个人学习总结&#xff0c;仅供参考。 一、Linux系统部署 二、服务器初始化 三、文件和用户管理 四、用户的权限 五、进程及进程管理 提示&#xff1a;文档陆续更新整理 进程及进程管理 Linux基础一、进程简介&#xff08;一&#xff09;进程三问…

面向万物智联的应用框架的思考和探索(上)

原文&#xff1a;面向万物智联的应用框架的思考和探索&#xff08;上&#xff09;&#xff0c;点击链接查看更多技术内容。 应用框架&#xff0c;是操作系统连接开发者生态&#xff0c;实现用户体验的关键基础设施。其中&#xff0c;开发效率和运行体验是永恒的诉求&#xff0c…

代码命名规范是真优雅呀!代码如诗

日常编码中&#xff0c;代码的命名是个大的学问。能快速的看懂开源软件的代码结构和意图&#xff0c;也是一项必备的能力。那它们有什么规律呢&#xff1f; Java项目的代码结构&#xff0c;能够体现它的设计理念。Java采用长命名的方式来规范类的命名&#xff0c;能够自己表达…

网络安全怎么学,才不会成为脚本小子?

一&#xff0c;怎么入门&#xff1f; 1、Web 安全相关概念&#xff08;2 周&#xff09; 了解网络安全相关法律法规 熟悉基本概念&#xff08;SQL 注入、上传、XSS、CSRF、一句话木马等&#xff09;。 通过关键字&#xff08;SQL 注入、上传、XSS、CSRF、一句话木马等&#…

RK3588 lt16911uxc hdmi in

我们的单板需要四路HDMI IN,其中一路使用了rk3588自带的hdmi rx,另外三路通过LT6911UXC 转接芯片,实现了三路hdmi in 使用了rk3588的DPHY0,DPHY1以及DCPHY0 设备树修改如下: /* DCPHY0 */ &csi2_dcphy0 { status = "okay"; ports { #addres…

【Errors】【计算机图形学】A-SDF复现的一点纠正记录

ICCV 2021的工作A-SDF&#xff0c;在跑的过程中可能是一些版我Run了这篇工作代码的Reconstruction&#xff0c;然后出现了一点小小的错误&#xff0c;记录如下。 问题一&#xff1a;对数据做直接修改导致出错&#xff08;可能是不同的pytorch版本导致的?&#xff09; 错误描述…

Notepad++ 配置python环境及虚拟环境和其它编程语言环境

背景&#xff1a; 在执行某些项目的时候&#xff0c;经常会否遇到这样一种情况(以python语言为例)&#xff1a;不想在python的基础环境下运行&#xff0c;创建了虚拟环境来装相关项目的包&#xff0c;但是每次使用都要切换到虚拟环境下面使用"activate "命令激活环境…

AT24C02芯片介绍

AT24C02管脚介绍 AT24C02低功耗CMOS串行EEPROM&#xff0c;它是内含2568位存储空间&#xff0c;具有工作电压宽&#xff08;2.5&#xff5e;5.5V&#xff09;、擦写次数多&#xff08;大于10000次&#xff09;、写入速度快&#xff08;小于10ms&#xff09;等特点。 AT24C02的1…

Elasticsearch --- 数据同步、集群

一、数据同步 elasticsearch中的酒店数据来自于mysql数据库&#xff0c;因此mysql数据发生改变时&#xff0c;elasticsearch也必须跟着改变&#xff0c;这个就是elasticsearch与mysql之间的数据同步。 思路分析&#xff1a; 常见的数据同步方案有三种&#xff1a; 同步调用 …

JavaScript:链表

文章目录 链表js没有链表吗203. 移除链表元素为什么设置虚拟头节点思路与代码分析上面代码补充分析注意&#xff1a;为什么把虚拟头节点赋值给 cur 204. 设计链表206. 反转链表双指针法--具体思路见代码双指针法具体分析&#xff08;上面代码看懂这里可以忽略&#xff09;递归法…

注册openai用什么邮箱-中文版ChatGPT有哪些

注册openai用什么邮箱 你可以使用任何有效的电子邮箱地址注册 OpenAI&#xff0c;例如 Gmail、Outlook、Yahoo 等。请注意&#xff0c;您需要通过验证您的电子邮件地址才能完成注册过程。 中文版ChatGPT哪个好 中文版的 ChatGPT 在自然语言处理、语言生成等领域拥有广泛的应用…

4.4.1内核编译

内核源码下载地址&#xff1a; https://mirrors.edge.kernel.org/pub/linux/kernel/v4.x/linux-4.4.1.tar.gz 安装依赖包&#xff1a;报错就装 cp /boot/config-xxx ./.config make mrproper make menuconfig,然后save保存&#xff0c;退出 make -j4 //四线程编译 sudo ma…

电气电工相关专业知识及名词解释

一、电流电压 火线、零线、地线&#xff1a;火线和零线的区别就是&#xff1a;火线带电&#xff0c;零线不带电。火线是传电流的&#xff0c;而零线是回流的。 红色是火线&#xff0c;零线一般是绿色的&#xff0c;通常可用电笔来测。电笔一头亮了是火线&#xff0c;不亮的则…

网络计算模式复习(一)

二层C/S架构 概念&#xff1a;C/S架构是一种典型的两层架构&#xff0c;其全称是Client/Server&#xff0c;即客户端服务器端架构。 其中客户端包含一个或多个在用户的电脑上运行的程序。服务器端有两种&#xff0c;一种是数据库服务器端&#xff0c;客户通过数据库连接访问服…

GBASE荣获2023数字中国创新大赛·信创赛道华北赛区一等奖

日前&#xff0c;2023数字中国创新大赛信创赛道华北赛区区域赛圆满结束。经过激烈角逐&#xff0c;GBASE参赛作品「多模多态企业级分布式数据库」GBase 8c荣获华北赛区一等奖&#xff0c;将被选送到全国总决赛&#xff0c;参与最后的冠军角逐。 数字中国创新大赛信创赛道是我国…

RIS/PACS系统源码,工作站无缝集成三维重建模块,实现极速的三维后处理

RIS/PACS系统源码 带三维重建和还原的PACS源码 RIS/PACS系统源码在预约登记、分诊叫号、技师检查、诊断报告、临床浏览、科室管理等环节满足全院相关科室的要求。在医学影像下载、浏览、处理中满足速度快、强化常用功能、方便阅片等要求。满足放射、超声、内镜、病理等影像科…

REMIX:重构·连接·进化|徐亚波博士D3大会演讲实录

“欢迎大家和数说故事一起来到新世界&#xff0c;和我们一起&#xff0c;来玩一个AI普适场景的无限游戏。” 在数说故事第六届D3智能营销峰会上&#xff0c;数说故事创始人兼CEO徐亚波博士带来「REMIX——重构连接进化」的主题分享&#xff0c;聚焦“ChatGPT开启的AGI时代有什么…

分块思想(Sqrt Decomposition)的实现(golang)

前言 Sqrt Decomposition 是一种数据结构&#xff0c;能够在O(1)时间复杂度内完成数组元素值的查询和更新&#xff0c;在 O ( n ) O(\sqrt{n}) O(n ​) 时间复杂度内完成数组某个区间属性值的查询和批量更新某个区间的值。这里的属性 可以是区间的和、最小值、最大值等。 说到…

DDR3(MIG核配置官方demoFPGA代码实现及仿真)

由于直接对 DDR3 进行控制很复杂&#xff0c;因此一般使用 MIG IP 来实现&#xff0c;同时为了更简单地使用 MIG IP&#xff0c;我们采用 AXI4 总线协议进行控制。下面首先介绍 MIG IP 的配置&#xff0c;然后看看官方 demo &#xff08;里面包含一个仿真要用到的 DDR3 模型&am…

Android Jetpack:利用Palette进行图片取色

与产品MM那些事 新来一个产品MM&#xff0c;因为比较平&#xff0c;我们就叫她A妹吧。A妹来第一天就指出&#xff1a;页面顶部的Banner广告位的背景是白色的&#xff0c;太单调啦&#xff0c;人家不喜欢啦&#xff0c;需要根据广告图片的内容自动切换背景颜色&#xff0c;颜色…