IT数码 购物 网址 头条 软件 日历 阅读 图书馆
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
图片批量下载器
↓批量下载图片,美女图库↓
图片自动播放器
↓图片自动播放器↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁
 
   -> C++知识库 -> C语言课设——通讯录(静态、动态、文件三版合一) -> 正文阅读

[C++知识库]C语言课设——通讯录(静态、动态、文件三版合一)

🌳🌲🌱本文已收录至:那些年我们写过的课设

更多知识尽在此专栏中!

?

🎉🎉🎉欢迎点赞、收藏、关注?🎉🎉🎉

目录

🌳前言

🌳正文

🌲核心功能讲解

🌲静态版

🌱增加信息

🌱删除信息

🌱姓名排序

🌱注意事项

🌲动态版

🌱动态开辟

🌱枚举常量

🌱内存归还

🌱注意事项

🌲文件版

🌱文件加载

🌱全面排序

🌱信息保存

🌱注意事项

? ? ? ? ?——————源码区——————

🌲静态版

🌱Contacts.h 功能声明头文件

🌱Contacts.c 功能实现源文件

🌱test.c? ? ? ? ?主函数源文件

🌲动态版

🌱Contacts.h 功能声明头文件

🌱Contacts.c 功能实现源文件

🌱test.c? ? ? ? ?主函数源文件

🌲文件操作版

🌱Contacts.h 功能声明头文件

🌱Contacts.c 功能实现源文件

🌱test.c? ? ? ? ?主函数源文件

🌳总结


🌳前言

? 相信每个科班的同学都有过C语言课设的经历,比如教职工工资管理系统、图书信息管理系统、学生信息管理系统、通讯录系统等,其实这些课设任务的底层逻辑都是一致的,无非就是对结构体变量进行增删查改操作,同时配合文件操作将数据保存在文件夹中,本文将以通讯录举例,从静态版到文件版,让大家明白通讯录系统是如何逐步完善的。

注意:文末有三个版本的所有源码,系统分为三个文件夹,即声明功能实现函数的头文件 Contacts.h 、实现各种功能函数的源文件 Contacts.c 、包含主函数及各种功能调用的源文件 test.c 。三个文件相辅相成,灵活强大。其中文件版由动态版迭代而来,而动态版是静态版的改进版本,因此三个版本中大部分代码都是一致的,为了突出差异,特地分成了三个板块。

?


🌳正文

? 首先先介绍一下不同版本中的核心功能(其他部分也挺重要的,但因文章篇幅限制,挑出了相对重要的部分讲解,当然后面源码区中有注释)

🌲核心功能讲解

🌲静态版

? 静态版的通讯录是最原始的版本,通讯录大小在程序设计时就已经被预设好了,如果存储信息数大于预设值,会提示内存已满,当然可以将预设值设计得非常大,但这极有可能用不完,会造成浪费,抛开容量这个问题来说,静态版通讯录功能还是挺全的,主要用到了自定义类型的知识

#define MAX 100    //这是静态版中,通讯录的预设大小

🌱增加信息

? 通讯录包含了姓名、性别、年龄等信息,是一个结构体类型的数据。

//联系人信息
struct PeoInfo
{
	char name[MAX_NAME];
	char sex[MAX_SEX];
	int age;
	char number[MAX_NUMBER];
	char address[MAX_ADDRESS];
};

//包含下标的信息
typedef struct Contact
{
	struct PeoInfo data[MAX];
	int sz;
}Con;

? 那么对通讯录增加信息,本质上就是在对结构体进行赋值,这里直接分语句对结构体中不同成员进行赋值。当然赋值前要先进行容量判断,看当前结构体下标是否等于预设值,如果等于,就无法添加信息,当前功能执行失败;小于的话能正常赋值,不过要记得赋值一组数据后,结构体下标要+1,避免下次对同一组数据进行重复赋值。

void ConAdd(Con* pc)//增加信息
{
	assert(pc);
	if (pc->sz == MAX)
	{
		printf("内存已满,请尝试删除部分联系人!\n");
		return;
	}
	//逐个输入
	printf("请输入姓名:>");
	scanf("%s", pc->data[pc->sz].name);
	printf("请输入性别:>");
	scanf("%s", pc->data[pc->sz].sex);
	printf("请输入年龄:>");
	scanf("%d", &(pc->data[pc->sz].age));
	printf("请输入号码:>");
	scanf("%s", pc->data[pc->sz].number);
	printf("请输入住址:>");
	scanf("%s", pc->data[pc->sz].address);

	pc->sz++;//每添加一条信息,长度就+1
	printf("增加成功!\n");
}

注意:对结构体的大多数操作,都需要传地址,因为要对结构体本身进行修改。赋值时,要理清两个结构体间的关系,确保数据不会赋错地方。?

🌱删除信息

? 删除信息本质上就是覆盖,找到想要删除的联系人所对应的下标,根据此下标,逐级将后面的结构体数据赋给前面的结构体,这样就完成了删除操作。

?

? 将上面的图解转换为代码展示如下(寻找下标需要额外封装一个查找函数):

static int Find(const Con* pc, const char* name)//辅助查找函数
{
	assert(pc);
	int i = 0;
	for (i = 0; i < pc->sz; i++)
	{
		if (strcmp(name, pc->data[i].name) == 0)
			return i;//返回下标
	}
	return -1;//没有找到
}

void ConErase(Con* pc)//删除信息
{
	assert(pc);
	if (!(pc->sz))
	{
		printf("当前通讯录为空,请尝试添加联系人!\n");
		return;
	}
	char name[MAX_NAME] = "0";//临时存储待查找姓名
	printf("请输入你想删除联系人的姓名:>");
	scanf("%s", name);
	if (Find(pc, name) == -1)
	{
		printf("没有找到此联系人!\n");
	}
	else
	{
		int i = Find(pc, name);
		for (; i < pc->sz - 1; i++)
		{
			pc->data[i] = pc->data[i + 1];
		}
		pc->sz--;//删除完后,长度-1
		printf("删除成功!\n");
	}
}

注意:通讯录删除的底层逻辑是覆盖,对于当前程序来说(顺序表),删除是比较麻烦的,如果通讯录内数据很多,每次删除都会浪费 O(n) 的时间去完成覆盖,改变这一缺点的方法是采用链式存储。?

🌱姓名排序

? 通讯录中的信息存储在一个结构体变量中,普通的排序无法完成任务,因此这里用到了C语言中的库函数 qsort ,它可以适用于所有数据类型的排序,忘记怎么使用的可以点这里

? 有了 qsort 的加持,排序就变得很简单了,这里按姓名进行排序,比较函数在设计时需要将 e1、e2 转为对应的结构体指针类型,才能成功访问到姓名这个数据域。

static int cmp(const void* e1, const void* e2)
{
	return strcmp(((struct PeoInfo*)e1)->name, ((struct PeoInfo*)e2)->name);
}

void ConSortByName(Con* pc)//按名字排序
{
	assert(pc);
	if (pc->sz == 0)
	{
		printf("当前通讯录为空,无法排序!\n");
		return;
	}
	else
	{
		qsort(pc, pc->sz, sizeof(struct PeoInfo), cmp);//快速排序
		printf("排序已完成,信息如下:>\n\n");
		ConPrint(pc);
	}
}

注意: qsort 在传递第三个参数(待排序数据大小)时,要特别注意,需要排序的数据大小为基本信息结构体的大小,不能错写成带下标结构体大小,这样在排序时信息是对不上的。排序完成后,直接调用打印函数,展示排序后的信息。

🌱注意事项

  • 1.结构体类型在设计时,为基本信息+带下标结构体的模式,其中后者包含前者
  • 2.打印通讯录时,格式化数据要和提示行数据对应上,比如 姓名-name
  • 3.增加一组信息,下标+1;删除一组信息,下标-1
  • 4.全部删除信息,就是将当前通讯录进行初始化,下标会归0
  • 5.在进行排序时,需要注意逻辑设计,如果是按姓名排,比较函数就要使用字符比较的方式;如果是按年龄排,用整型数据比较的方式

🌲动态版

? 动态版解决了静态版最大的痛点——最大容量不好设置,动态版通讯录用到了动态内存管理的知识,遵循用多少、申请多少的原则,动态版通讯录能够无限空间且不会造成浪费,需要注意的是动态开辟的空间,在通讯录结束时要归还给操作系统。

🌱动态开辟

? 为了满足动态内存开辟的需求,将静态版通讯录中的结构体类型进行了重新设计,将原来的数组模式改为指针类型(方便节点申请),新增容量这个成员,当下标等于容量时,进行扩容,在原来基础上申请更多的空间来存储数据,关于动态内存开辟可以点这里

//联系人信息
struct PeoInfo
{
	char name[MAX_NAME];
	char sex[MAX_SEX];
	int age;
	char number[MAX_NUMBER];
	char address[MAX_ADDRESS];
};

typedef struct Contact
{
	struct PeoInfo* data;
	int sz;//下标
	int capacity;//容量
}Con;

? 空间开辟函数是独立封装的,当我们增加联系人信息时,会判断空间是否已达到容量值,如果达到了,进入扩容函数,申请足够的空间,成功后将容量和指针信息更新即可。

void checkCapacity(Con* pc)
{
	int newCapacity = (pc->capacity) * INC_SZ;
	struct PeoInfo* ret = (struct PeoInfo*)realloc(pc->data, sizeof(struct PeoInfo) * newCapacity);
	assert(ret);
	pc->capacity = newCapacity;
	pc->data = ret;
	printf("增容成功!\n");
}

注意:动态内存开辟后要遵循开辟规则,对返回的指针进行判断,看是否扩容失败,如果失败了,要及时终止程序。当然在程序运行结束后,要记得释放内存

🌱枚举常量

? 枚举常量的存在可以使case语句更清晰,而不是依赖于数字1、2、3,见名知意,是一种提高程序可读性的好方法,关于枚举常量的介绍可以点这里

enum Menu
{
	Exit,
	Name,
	Sex,
	Age,
	Number,
	Address
};    //在case语句中,Exit就可以直接表示为整型 0
//实际运用
case Exit:
			printf("中止修改!\n");
			break;

注意:在使用枚举常量时,要注意默认从0开始往后枚举,如果需要指定枚举值,需要提前设定,确保枚举常量的正确性。

🌱内存归还

? 内存归还就是释放之前开辟的空间,可以将这个函数放在退出通讯录的地方。因为之前开辟的空间是连续的,所以直接释放指针指向空间的数据(结构体起始位置),释放后指针置空,下标和容量归零就可以了。

void ConDestroy(Con* pc)//销毁通讯录
{
	assert(pc);
	free(pc->data);
	pc->data = NULL;
	pc->sz = pc->capacity = 0;
}

注意:释放的空间必须是已经申请好的,释放后指针要置空,避免野指针,下标和容量置空,确保销毁通讯录的全面性。

🌱注意事项

  • 1.动态版通讯录在初始化时,需要先申请默认大小的空间,容量也要设为默认值
  • 2.动态开辟时,要注意大小申请的匹配性,为基本信息结构体大小
  • 3.其他操作与静态版基本一致,也可以通过下标访问操作符配合指针,访问成员变量
  • 4.在进行排序时,操作对象为 pc->data,即基本信息结构体
  • 5.内存归还时,要合情合理,不能随意操作未开辟/已归还的空间

🌲文件版

? 文件版在动态版的基础上进行了改进,可以从文件中读取到已有的联系人信息,或把新获取的联系人信息存入文件夹中,做到数据的持久化存储,这会用到文件操作相关知识,可以点这里回顾知识

🌱文件加载

? 文件加载函数可以放在初始化函数中,当然文件加载前还需要判断当前通讯录容量是否足够,因此需要对扩容函数进行声明确保其能在初始化函数中使用。

void ConLoad(Con* pc)//加载通讯录信息
{
	assert(pc);
	FILE* fp = fopen("Contact.txt", "r");//打开文件
	if (NULL == fp)
	{
		perror("fopen::Contact.txt");
		return;
	}
	//读数据
	struct PeoInfo tmp = { 0 };//临时存储
	int ret = 0;
	char ch[10] = "0";
	fscanf(fp, "%s %s %s %s %s", ch, ch, ch, ch, ch);    //读取标头信息
	while (ret = (fscanf(fp, "%s %s %d %s %s", tmp.name, tmp.sex, 
		&(tmp.age), tmp.number, tmp.address)) >= 1)
	{
		if (pc->sz == pc->capacity)
			buyNewCapacity(pc);//扩容
		pc->data[pc->sz] = tmp;
		pc->sz++;
	}
	if (feof(fp))
		printf("\nEnd by EOF\n");
	else if (ferror(fp))
		printf("\nEnd by IO\n");

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

注意:文件加载遵循文件打开三步走,即打开文件、使用文件、关闭文件,在打开文件后,要对文件指针进行空指针判断。加载文件时,会读取文件中的标头信息,在循环读取通讯录数据,这里采用了格式化读取,每读取成功一个数据,下标+1。

🌱全面排序

? 全面排序在按姓名排序的基础上做了升级,现在能通过菜单,选择不同的排序逻辑:按姓名、按年龄、按地址……为了适用于所有的排序,设计出了不同排序比较函数。

//各种排序的比较函数
int cmpByName(const void* e1, const void* e2)
{
	return strcmp(((struct PeoInfo*)e1)->name, ((struct PeoInfo*)e2)->name);	//按姓名
}
int cmpBySex(const void* e1, const void* e2)
{
	return strcmp(((struct PeoInfo*)e1)->sex, ((struct PeoInfo*)e2)->sex);	//按性别
}
int cmpByAge(const void* e1, const void* e2)
{
	return (((struct PeoInfo*)e1)->age) - (((struct PeoInfo*)e2)->age);	//按年龄
}
int cmpByNumber(const void* e1, const void* e2)
{
	return strcmp(((struct PeoInfo*)e1)->number, ((struct PeoInfo*)e2)->number);	//按电话号码
}
int cmpByAddress(const void* e1, const void* e2)
{
	return strcmp(((struct PeoInfo*)e1)->address, ((struct PeoInfo*)e2)->address);	//按地址
}

注意:不同的成员变量所需要的比较函数不同,需要根据需求设计。?

🌱信息保存

? 信息保存即文件写入操作,将当前程序中结构体的数据写入到文件中,正式写入数据前需要先写入标头信息,通过 for 循环将通讯录中的数据全部写入文件中。

void ConSave(Con* pc)//通讯录信息保存
{
	assert(pc);
	FILE* fp = fopen("Contact.txt", "w");//打开文件
	if (NULL == fp)
	{
		perror("fopen::Contact.txt");
		return;
	}
	//写文件
	fprintf(fp, "%-10s\t%-5s\t%-s\t%-20s\t%-30s\n", "姓名", "性别", "年龄", "号码", "住址");	//写入标头信息
	int i = 0;
	for (i = 0; i < pc->sz; i++)
	{
		fprintf(fp, "%-10s\t%-5s\t%-d\t%-15s\t%-30s\n", pc->data[i].name, pc->data[i].sex,
			pc->data[i].age, pc->data[i].number, pc->data[i].address);
	}

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

注意:文件写入的操作指令是 "w" ,指令给错后将无法写入数据。格式化写入同格式化读取一样,格式一定要匹配上。记得关闭文件,并把文件指针置空?

🌱注意事项

  • 1.文件版通讯录核心在于文件读取和写入操作,需要对文件操作有一定的了解
  • 2.在读取文件前,务必确保目标文件存在,否则会读取失败
  • 3.如果想在原来数据基础上追加数据,需要配合指令 "a"

? ? ? ? ?——————源码区——————


? 下面是不同版本的源码,文件版为重新编写的版本,在部分变量和函数命名上可能与前两个版本有差异,但底层逻辑是一致的。

🌲静态版

🌱Contacts.h 功能声明头文件

#pragma once
#include<stdio.h>
#include<stdlib.h>
#include<string.h>
#include<assert.h>
#include<windows.h>

#define MAX 100
#define MAX_NAME 10
#define MAX_SEX 5
#define MAX_NUMBER 15
#define MAX_ADDRESS 30

//联系人信息
struct PeoInfo
{
	char name[MAX_NAME];
	char sex[MAX_SEX];
	int age;
	char number[MAX_NUMBER];
	char address[MAX_ADDRESS];
};

//包含下标的信息
typedef struct Contact
{
	struct PeoInfo data[MAX];
	int sz;
}Con;

void ConInit(Con* pc);//初始化通讯录
void ConPrint(const Con* pc);//打印通讯录

void ConAdd(Con* pc);//增加信息
void ConErase(Con* pc);//删除信息
void ConFind(const Con* pc);//查找信息
void ConRevise(Con* pc);//修改信息
void ConEraseAll(Con* pc);//全部删除
void ConSortByName(Con* pc);//按名字排序

🌱Contacts.c 功能实现源文件

#define _CRT_SECURE_NO_WARNINGS 1
#include"Contact.h"

static void menu()
{
	printf("*******************************\n");
	printf("******1.Name     2.Age   ******\n");
	printf("******3.Sex      4.Number******\n");
	printf("******5.Address  6.Exit  ******\n");
	printf("*******************************\n");
}

void ConInit(Con* pc)//初始化通讯录
{
	assert(pc);
	pc->sz = 0;//下标归零
	memset(pc->data, 0, sizeof(struct PeoInfo) * MAX);//内存设置,初始化
}

void ConPrint(const Con* pc)//打印通讯录
{
	assert(pc);
	if (pc->sz == 0)
	{
		printf("当前通讯录为空,请尝试添加联系人!\n");
		return;
	}
	int i = 0;
	//经测试,发现使用宏定义的常量,如 MAX_NAME 代替 10,打印会出错
	printf("%-10s\t%-5s\t%-s\t%-15s\t%-30s\n", "姓名", "性别", "年龄", "号码", "住址");
	for (i = 0; i < pc->sz; i++)
	{
		printf("%-10s\t%-5s\t%-d\t%-15s\t%-30s\n", pc->data[i].name, pc->data[i].sex, pc->data[i].age,
			pc->data[i].number, pc->data[i].address);
	}
}

void ConAdd(Con* pc)//增加信息
{
	assert(pc);
	if (pc->sz == MAX)
	{
		printf("内存已满,请尝试删除部分联系人!\n");
		return;
	}
	//逐个输入
	printf("请输入姓名:>");
	scanf("%s", pc->data[pc->sz].name);
	printf("请输入性别:>");
	scanf("%s", pc->data[pc->sz].sex);
	printf("请输入年龄:>");
	scanf("%d", &(pc->data[pc->sz].age));
	printf("请输入号码:>");
	scanf("%s", pc->data[pc->sz].number);
	printf("请输入住址:>");
	scanf("%s", pc->data[pc->sz].address);

	pc->sz++;//每添加一条信息,长度就+1
	printf("增加成功!\n");
}

static int Find(const Con* pc, const char* name)//辅助查找函数
{
	assert(pc);
	int i = 0;
	for (i = 0; i < pc->sz; i++)
	{
		if (strcmp(name, pc->data[i].name) == 0)
			return i;//返回下标
	}
	return -1;//没有找到
}

void ConErase(Con* pc)//删除信息
{
	assert(pc);
	if (!(pc->sz))
	{
		printf("当前通讯录为空,请尝试添加联系人!\n");
		return;
	}
	char name[MAX_NAME] = "0";//临时存储待查找姓名
	printf("请输入你想删除联系人的姓名:>");
	scanf("%s", name);
	if (Find(pc, name) == -1)
	{
		printf("没有找到此联系人!\n");
	}
	else
	{
		int i = Find(pc, name);
		for (; i < pc->sz - 1; i++)
		{
			pc->data[i] = pc->data[i + 1];
		}
		pc->sz--;//删除完后,长度-1
		printf("删除成功!\n");
	}
}

void ConFind(const Con* pc)//查找信息
{
	assert(pc);
	printf("请输入你想查找联系人的姓名:>");
	char name[MAX_NAME] = "0";//临时存储待查找姓名
	scanf("%s", name);
	int ret = Find(pc, name);//判断变量
	if (ret == -1)
	{
		printf("没有找到这个联系人!\n");
	}
	else
	{
		printf("此联系人信息如下:\n\n");
		printf("%-10s\t%-5s\t%-s\t%-15s\t%-30s\n", "姓名", "性别", "年龄", "号码", "住址");
		printf("%-10s\t%-5s\t%-d\t%-15s\t%-30s\n", pc->data[ret].name, pc->data[ret].sex, pc->data[ret].age,
			pc->data[ret].number, pc->data[ret].address);
	}
}

void ConRevise(Con* pc)//修改信息
{
	assert(pc);
	char name[MAX_NAME] = "0";//存储查找人的姓名
	printf("请输入你想修改联系人的姓名:>");
	scanf("%s", name);
	int ret = Find(pc, name);//判断变量
	if (ret == -1)
	{
		printf("没有找到这个联系人!\n");
	}
	else
	{
		int input = 0;
		menu();//菜单,不同于主函数中的menu
		printf("请选择你想修改的信息:>");
		scanf("%d", &input);
		//分类讨论,本来想再封装一个函数,考虑到每次修改内容都不同
		//就没有设计了
		switch (input)
		{
		case 1:
			printf("请输入你想修改的值:>");
			scanf("%s", pc->data[ret].name);
			printf("修改成功!\n");
			break;
		case 2:
			printf("请输入你想修改的值:>");
			scanf("%s", pc->data[ret].sex);
			printf("修改成功!\n");
			break;
		case 3:
			printf("请输入你想修改的值:>");
			scanf("%d", &(pc->data[ret].age));
			printf("修改成功!\n");
			break;
		case 4:
			printf("请输入你想修改的值:>");
			scanf("%s", pc->data[ret].number);
			printf("修改成功!\n");
			break;
		case 5:
			printf("请输入你想修改的值:>");
			scanf("%s", pc->data[ret].address);
			printf("修改成功!\n");
			break;
		case 0:
			printf("中止修改!\n");
			break;
		default:
			printf("选择有误!\n");
			break;
		}
	}
}

void ConEraseAll(Con* pc)//全部删除
{
	assert(pc);
	ConInit(pc);//直接初始化,就是全部删除
	printf("通讯录中的所有信息都已重置!\n");
}

static int cmp(const void* e1, const void* e2)
{
	return strcmp(((struct PeoInfo*)e1)->name, ((struct PeoInfo*)e2)->name);
}

void ConSortByName(Con* pc)//按名字排序
{
	assert(pc);
	if (pc->sz == 0)
	{
		printf("当前通讯录为空,无法排序!\n");
		return;
	}
	else
	{
		qsort(pc, pc->sz, sizeof(struct PeoInfo), cmp);//快速排序
		//出现bug,原因快排设计大小为char,与类型不匹配
		printf("排序已完成,信息如下:>\n\n");
		ConPrint(pc);
	}
}

🌱test.c? ? ? ? ?主函数源文件

#define _CRT_SECURE_NO_WARNINGS 1
//实现通讯录
#include"Contact.h"

//第一代通讯录,无bug
//刷新git提交

void menu()
{
	printf("******   通讯录 1.0   ********\n");
	printf("******************************\n");
	printf("******1.Add     2.Del   ******\n");
	printf("******3.Find    4.Revise******\n");
	printf("******5.Show    6.Empty ******\n");
	printf("******7.Sort    0.Exit  ******\n");
	printf("******************************\n");
}
int main()
{
	Con C;
	ConInit(&C);
	int input = 0;
	do
	{
		menu();
		printf("请选择:>");
		scanf("%d", &input);
		system("cls");
		switch (input)
		{
		case 1:
			ConAdd(&C);
			break;
		case 2:
			ConErase(&C);
			break;
		case 3:
			ConFind(&C);
			break;
		case 4:
			ConRevise(&C);
			break;
		case 5:
			ConPrint(&C);
			break;
		case 6:
			ConEraseAll(&C);
			break;
		case 7:
			ConSortByName(&C);
			break;
		case 0:
			printf("退出通讯录!\n");
			break;
		default:
			printf("选择错误,请重新选择!\n");
			break;
		}
	} while (input);
	return 0;
}

🌲动态版

🌱Contacts.h 功能声明头文件

#pragma once
#include<stdio.h>
#include<stdlib.h>
#include<string.h>
#include<assert.h>
#include<windows.h>

#define MAX 100
#define MAX_NAME 10
#define MAX_SEX 5
#define MAX_NUMBER 15
#define MAX_ADDRESS 30

#define DEFAULT_SIZE 2
#define INC_SZ 2

//联系人信息
struct PeoInfo
{
	char name[MAX_NAME];
	char sex[MAX_SEX];
	int age;
	char number[MAX_NUMBER];
	char address[MAX_ADDRESS];
};

typedef struct Contact
{
	struct PeoInfo* data;
	int sz;//下标
	int capacity;//容量
}Con;

void ConInit(Con* pc);//初始化通讯录
void ConPrint(const Con* pc);//打印通讯录

void ConAdd(Con* pc);//增加信息
void ConErase(Con* pc);//删除信息
void ConFind(const Con* pc);//查找信息
void ConRevise(Con* pc);//修改信息
void ConEraseAll(Con* pc);//全部删除
void ConSortByName(Con* pc);//按名字排序
void ConDestroy(Con* pc);//销毁通讯录

🌱Contacts.c 功能实现源文件

#define _CRT_SECURE_NO_WARNINGS 1
#include"Contacts.h"

static void menu()
{
	printf("*******************************\n");
	printf("******1.Name     2.Sex   ******\n");
	printf("******3.Age      4.Number******\n");
	printf("******5.Address  0.Exit  ******\n");
	printf("*******************************\n");
}

enum Menu
{
	Exit,
	Name,
	Sex,
	Age,
	Number,
	Address
};

void ConInit(Con* pc)//初始化通讯录
{
	assert(pc);
	pc->data = (struct PeoInfo*)malloc(sizeof(struct PeoInfo) * DEFAULT_SIZE);
	if (pc->data == NULL)
	{
		perror("ConInit");
		return;
	}
	pc->sz = 0;//下标归零
	pc->capacity = DEFAULT_SIZE;//容量归0
}

void ConPrint(const Con* pc)//打印通讯录
{
	assert(pc);
	if (pc->sz == 0)
	{
		printf("当前通讯录为空,请尝试添加联系人!\n");
		return;
	}
	int i = 0;
	//经测试,发现使用宏定义的常量,如 MAX_NAME 代替 10,打印会出错
	printf("%-10s\t%-5s\t%-s\t%-15s\t%-30s\n", "姓名", "性别", "年龄", "号码", "住址");
	for (i = 0; i < pc->sz; i++)
	{
		printf("%-10s\t%-5s\t%-d\t%-15s\t%-30s\n", pc->data[i].name, pc->data[i].sex, pc->data[i].age,
			pc->data[i].number, pc->data[i].address);
	}
}

void checkCapacity(Con* pc)
{
	int newCapacity = (pc->capacity) * INC_SZ;
	struct PeoInfo* ret = (struct PeoInfo*)realloc(pc->data, sizeof(struct PeoInfo) * newCapacity);
	assert(ret);
	pc->capacity = newCapacity;
	pc->data = ret;
	printf("增容成功!\n");
}

void ConAdd(Con* pc)//增加信息
{
	assert(pc);
	if (pc->sz == pc->capacity)
		checkCapacity(pc);

	//逐个输入
	printf("请输入姓名:>");
	scanf("%s", pc->data[pc->sz].name);
	printf("请输入性别:>");
	scanf("%s", pc->data[pc->sz].sex);
	printf("请输入年龄:>");
	scanf("%d", &(pc->data[pc->sz].age));
	printf("请输入号码:>");
	scanf("%s", pc->data[pc->sz].number);
	printf("请输入住址:>");
	scanf("%s", pc->data[pc->sz].address);

	pc->sz++;//每添加一条信息,长度就+1
	printf("增加成功!\n");
}

static int Find(const Con* pc, const char* name)//辅助查找函数
{
	assert(pc);
	int i = 0;
	for (i = 0; i < pc->sz; i++)
	{
		if (strcmp(name, pc->data[i].name) == 0)
			return i;//返回下标
	}
	return -1;//没有找到
}

void ConErase(Con* pc)//删除信息
{
	assert(pc);
	if (!(pc->sz))
	{
		printf("当前通讯录为空,请尝试添加联系人!\n");
		return;
	}
	char name[MAX_NAME] = "0";//临时存储待查找姓名
	printf("请输入你想删除联系人的姓名:>");
	scanf("%s", name);
	if (Find(pc, name) == -1)
	{
		printf("没有找到此联系人!\n");
	}
	else
	{
		int i = Find(pc, name);
		for (; i < pc->sz - 1; i++)
		{
			pc->data[i] = pc->data[i + 1];
		}
		pc->sz--;//删除完后,长度-1
		printf("删除成功!\n");
	}
}

void ConFind(const Con* pc)//查找信息
{
	assert(pc);
	printf("请输入你想查找联系人的姓名:>");
	char name[MAX_NAME] = "0";//临时存储待查找姓名
	scanf("%s", name);
	int ret = Find(pc, name);//判断变量
	if (ret == -1)
	{
		printf("没有找到这个联系人!\n");
	}
	else
	{
		printf("此联系人信息如下:\n\n");
		printf("%-10s\t%-5s\t%-s\t%-15s\t%-30s\n", "姓名", "性别", "年龄", "号码", "住址");
		printf("%-10s\t%-5s\t%-d\t%-15s\t%-30s\n", pc->data[ret].name, pc->data[ret].sex, pc->data[ret].age,
			pc->data[ret].number, pc->data[ret].address);
	}
}

void ConRevise(Con* pc)//修改信息
{
	assert(pc);
	char name[MAX_NAME] = "0";//存储查找人的姓名
	printf("请输入你想修改联系人的姓名:>");
	scanf("%s", name);
	int ret = Find(pc, name);//判断变量
	if (ret == -1)
	{
		printf("没有找到这个联系人!\n");
	}
	else
	{
		int input = 0;
		menu();//菜单,不同于主函数中的menu
		printf("请选择你想修改的信息:>");
		scanf("%d", &input);
		//分类讨论,本来想再封装一个函数,考虑到每次修改内容都不同
		//就没有设计了
		switch (input)
		{
		case Name:
			printf("请输入你想修改的值:>");
			scanf("%s", pc->data[ret].name);
			printf("修改成功!\n");
			break;
		case Sex:
			printf("请输入你想修改的值:>");
			scanf("%s", pc->data[ret].sex);
			printf("修改成功!\n");
			break;
		case Age:
			printf("请输入你想修改的值:>");
			scanf("%d", &(pc->data[ret].age));
			printf("修改成功!\n");
			break;
		case Number:
			printf("请输入你想修改的值:>");
			scanf("%s", pc->data[ret].number);
			printf("修改成功!\n");
			break;
		case Address:
			printf("请输入你想修改的值:>");
			scanf("%s", pc->data[ret].address);
			printf("修改成功!\n");
			break;
		case Exit:
			printf("中止修改!\n");
			break;
		default:
			printf("选择有误!\n");
			break;
		}
	}
}

void ConEraseAll(Con* pc)//全部删除
{
	assert(pc);
	ConInit(pc);//直接初始化,就是全部删除
	printf("通讯录中的所有信息都已重置!\n");
}

static int cmp(const void* e1, const void* e2)
{
	return strcmp(((struct PeoInfo*)e1)->name, ((struct PeoInfo*)e2)->name);
}

void ConSortByName(Con* pc)//按名字排序
{
	assert(pc);
	if (pc->sz == 0)
	{
		printf("当前通讯录为空,无法排序!\n");
		return;
	}
	else
	{
		qsort(pc->data, pc->sz, sizeof(struct PeoInfo), cmp);//快速排序
		//出现bug,原因快排设计大小为char,与类型不匹配
		//*bug已解决,排序已全面推广
		printf("排序已完成,信息如下:>\n\n");
		ConPrint(pc);
	}
}

void ConDestroy(Con* pc)//销毁通讯录
{
	assert(pc);
	free(pc->data);
	pc->data = NULL;
	pc->sz = pc->capacity = 0;
}

🌱test.c? ? ? ? ?主函数源文件

#define _CRT_SECURE_NO_WARNINGS 1
//实现通讯录
#include"Contacts.h"

//9_25 进行改造,改为动态版本

void menu()
{
	printf("******************************\n");
	printf("******1.Add     2.Del   ******\n");
	printf("******3.Find    4.Revise******\n");
	printf("******5.Show    6.Empty ******\n");
	printf("******7.Sort    0.Exit  ******\n");
	printf("******************************\n");
}
enum Menu
{
	Exit,
	Add,
	Del,
	Find,
	Revise,
	Show,
	Empty,
	Sort
};
int main()
{
	Con C;
	ConInit(&C);
	int input = 0;
	do
	{
		menu();
		printf("请选择:>");
		scanf("%d", &input);
		system("cls");
		switch (input)
		{
		case Add:
			ConAdd(&C);
			break;
		case Del:
			ConErase(&C);
			break;
		case Find:
			ConFind(&C);
			break;
		case Revise:
			ConRevise(&C);
			break;
		case Show:
			ConPrint(&C);
			break;
		case Empty:
			ConEraseAll(&C);
			break;
		case Sort:
			ConSortByName(&C);
			break;
		case Exit:
			printf("退出通讯录!\n");
			ConDestroy(&C);
			break;
		default:
			printf("选择错误,请重新选择!\n");
			break;
		}
	} while (input);
	return 0;
}

🌲文件操作版

🌱Contacts.h 功能声明头文件

#pragma once
#include<stdio.h>
#include<stdlib.h>
#include<windows.h>
#include<assert.h>

#define DEFAULT_SIZE 3 //默认通讯录大小为3,如果不够用,会扩容
#define INC_SZ 2 * DEFAULT_SIZE //每次扩容两倍

#define MAX_NAME 10
#define MAX_SEX 5
#define MAX_NUMBER 20
#define MAX_ADDRESS 50
struct PeoInfo
{
	char name[MAX_NAME];//姓名
	char sex[MAX_SEX];//性别
	int age;//年龄
	char number[MAX_NUMBER];//电话号码
	char address[MAX_ADDRESS];//住址
};

typedef struct ContactByPeo
{
	struct PeoInfo* data;//数据域
	int sz;//下标
	int capacity;//容量
}Con;

void ConInit(Con* pc);//初始化通讯录
void ConDisplay(const Con* pc);//打印通讯录
void ConDestroy(Con* pc);//销毁通讯录

//增删查改排
void ConAdd(Con* pc);//增加联系人信息
void ConDelte(Con* pc);//删除联系人信息
void ConFind(const Con* pc);//查找联系人信息
void ConRevise(Con* pc);//修改联系人信息
void ConSort(Con* pc);//对信息进行排序
int  ConInfoNum(const Con* pc);//统计通讯录中的信息数

void ConLoad(Con* pc);//加载通讯录信息
void ConSave(Con* pc);//通讯录信息保存

🌱Contacts.c 功能实现源文件

#define _CRT_SECURE_NO_WARNINGS 1	
#include"Contact.h"

void buyNewCapacity(Con* pc);//声明扩容函数
void ConLoad(Con* pc);//声明 加载通讯录信息

void ConInit(Con* pc)//初始化通讯录
{
	assert(pc);
	pc->data = (struct PeoInfo*)malloc(sizeof(struct PeoInfo) * DEFAULT_SIZE);
	if (NULL == pc->data)
	{
		//申请失败,终止初始化
		perror("malloc");
		return;
	}
	pc->sz = 0;//下标为0
	pc->capacity = DEFAULT_SIZE;//确定容量

	ConLoad(pc);//加载通讯录
}

void ConDisplay(const Con* pc)//打印通讯录
{
	assert(pc);
	if (pc->sz == 0)
	{
		printf("当前通讯录为空,请尝试添加联系人!\n");
		return;
	}
	printf("%-10s\t%-5s\t%-s\t%-20s\t%-30s\n", "姓名", "性别", "年龄", "号码", "住址");
	int i = 0;
	for (i = 0; i < pc->sz; i++)
	{
		printf("%-10s\t%-5s\t%-d\t%-20s\t%-30s\n", pc->data[i].name, pc->data[i].sex, 
			pc->data[i].age, pc->data[i].number, pc->data[i].address);
	}
}

void ConDestroy(Con* pc)//销毁通讯录
{
	assert(pc);
	free(pc->data);
	pc->data = NULL;
	pc->sz = pc->capacity = 0;
}

//扩容函数,如果下标碰到容量了,就需要扩容
void buyNewCapacity(Con* pc)
{
	assert(pc);
	struct PeoInfo* ret = (struct PeoInfo*)realloc(pc->data, sizeof(struct PeoInfo) * INC_SZ);
	if (NULL == ret)
	{
		//申请失败
		perror("relloc");
		return;
	}
	pc->data = ret;
	pc->capacity += INC_SZ;//容量增加
}

//增删查改排
void ConAdd(Con* pc)//增加联系人信息
{
	assert(pc);
	if (pc->capacity == pc->sz)
		buyNewCapacity(pc);//容量不足就扩容
	printf("请输入姓名:>");
	scanf("%s", pc->data[pc->sz].name);
	printf("请输入性别:>");
	scanf("%s", pc->data[pc->sz].sex);
	printf("请输入年龄:>");
	scanf("%d", &(pc->data[pc->sz].age));
	printf("请输入电话:>");
	scanf("%s", pc->data[pc->sz].number);
	printf("请输入住址:>");
	scanf("%s", pc->data[pc->sz].address);

	printf("信息增加完毕!\n");
	pc->sz++;
}

const int findByName(const Con* pc)//只允许在这个文件内使用
{
	assert(pc);
	char findName[MAX_NAME] = "0";
	printf("请输入想查找的联系人姓名:>");
	scanf("%s", findName);
	int i = 0;
	for (i = 0; i < pc->sz; i++)
	{
		if (strcmp(pc->data[i].name, findName) == 0)
			return i;
	}
	return -1;
}

void ConDelte(Con* pc)//删除联系人信息
{
	assert(pc);
	int ret = findByName(pc);//顺序表,可以返回下标
	if (ret >= 0)
	{
		int input = 0;
		printf("确认删除此人信息吗?取消输入0,否则输入任意数:>");
		scanf("%d", &input);
		if (input == 0)
		{
			printf("取消成功!\n");
			return;
		}
		printf("正在删除中…………\n");
		Sleep(1500);
		int i = ret;
		for (; i < pc->sz; i++)
			pc->data[i] = pc->data[i + 1];
		pc->sz--;
		printf("删除完成!\n");
	}
	else
		printf("查无此人!\n");
}

void DisplayByRet(const Con* pc, int ret)
{
	assert(pc);
	printf("已经找到这个人了,信息如下所示:\n");
	printf("%-10s\t%-5s\t%-s\t%-20s\t%-30s\n", "姓名", "性别", "年龄", "号码", "住址");
	printf("%-10s\t%-5s\t%-d\t%-20s\t%-30s\n", pc->data[ret].name, pc->data[ret].sex,
		pc->data[ret].age, pc->data[ret].number, pc->data[ret].address);
}
void ConFind(const Con* pc)//查找联系人信息
{
	assert(pc);
	int ret = findByName(pc);
	if (ret >= 0)
	{
		DisplayByRet(pc, ret);
		return;
	}
	else
		printf("查无此人!\n");
}

//服务于修改的菜单
void menuByReviseAndSort()
{
	printf("***************************************\n");
	printf("**********  0.终止  1.姓名  **********\n");
	printf("**********  2.性别  3.年龄  **********\n");
	printf("**********  4.电话  5.住址  **********\n");
	printf("***************************************\n");
}
enum MenuByReviseAndSort
{
	终止,姓名, 性别, 年龄, 电话, 住址
};

void ConRevise(Con* pc)//修改联系人信息
{
	assert(pc);
	//因为待修改的信息,有共同特征,因此可以用宏定义来实现
	int ret = findByName(pc);
	if (ret >= 0)
	{
		DisplayByRet(pc, ret);
		int input = 1;
		while (input)
		{
			menuByReviseAndSort();
			printf("请选择你想修改的信息:>");
			scanf("%d", &input);
			switch (input)
			{
			case 终止:
				printf("终止修改成功!\n");
				break;
			case 姓名:
				printf("请输入修改后的信息:>");
				scanf("%s", pc->data[ret].name);
				printf("修改成功!\n");
				break;
			case 性别:
				printf("请输入修改后的信息:>");
				scanf("%s", pc->data[ret].sex);
				printf("修改成功!\n");
				break;
			case 年龄:
				printf("请输入修改后的信息:>");
				scanf("%d", &(pc->data[ret].age));
				printf("修改成功!\n");
				break;
			case 电话:
				printf("请输入修改后的信息:>");
				scanf("%s", pc->data[ret].number);
				printf("修改成功!\n");
				break;
			case 住址:
				printf("请输入修改后的信息:>");
				scanf("%s", pc->data[ret].address);
				printf("修改成功!\n");
				break;
			default:
				printf("选择错误,请重新选择!\n");
				break;
			}
		}
	}
	else
		printf("查无此人!\n");
}

//各种排序的比较函数
int cmpByName(const void* e1, const void* e2)
{
	return strcmp(((struct PeoInfo*)e1)->name, ((struct PeoInfo*)e2)->name);	//按姓名
}
int cmpBySex(const void* e1, const void* e2)
{
	return strcmp(((struct PeoInfo*)e1)->sex, ((struct PeoInfo*)e2)->sex);	//按性别
}
int cmpByAge(const void* e1, const void* e2)
{
	return (((struct PeoInfo*)e1)->age) - (((struct PeoInfo*)e2)->age);	//按年龄
}
int cmpByNumber(const void* e1, const void* e2)
{
	return strcmp(((struct PeoInfo*)e1)->number, ((struct PeoInfo*)e2)->number);	//按电话号码
}
int cmpByAddress(const void* e1, const void* e2)
{
	return strcmp(((struct PeoInfo*)e1)->address, ((struct PeoInfo*)e2)->address);	//按地址
}

void ConSort(Con* pc)//对信息进行排序
{
	assert(pc);
	int input = 1;
	while (input)
	{
		menuByReviseAndSort();
		printf("选择排序方式:>");
		scanf("%d", &input);
		switch (input)
		{
		case 终止:
			printf("终止排序成功!\n");
			break;
		case 姓名:
			qsort(pc->data, pc->sz, sizeof(struct PeoInfo), cmpByName);
			printf("按性别排序已经完成了,信息如下:\n");
			ConDisplay(pc);
			break;
		case 性别:
			qsort(pc->data, pc->sz, sizeof(struct PeoInfo), cmpBySex);
			printf("按性别排序已经完成了,信息如下:\n");
			ConDisplay(pc);
			break;
		case 年龄:
			qsort(pc->data, pc->sz, sizeof(struct PeoInfo), cmpByAge);
			printf("按年龄排序已经完成了,信息如下:\n");
			ConDisplay(pc);
			break;
		case 电话:
			qsort(pc->data, pc->sz, sizeof(struct PeoInfo), cmpByNumber);
			printf("按电话排序已经完成了,信息如下:\n");
			ConDisplay(pc);
			break;
		case 住址:
			qsort(pc->data, pc->sz, sizeof(struct PeoInfo), cmpByAddress);
			printf("按住址排序已经完成了,信息如下:\n");
			ConDisplay(pc);
			break;
		default:
			printf("选择错误,请重新选择!\n");
			break;
		}
	}
}

int ConInfoNum(const Con* pc)//统计通讯录中的信息数
{
	assert(pc);
	return pc->sz;
}

void ConLoad(Con* pc)//加载通讯录信息
{
	assert(pc);
	FILE* fp = fopen("Contact.txt", "r");//打开文件
	if (NULL == fp)
	{
		perror("fopen::Contact.txt");
		return;
	}
	//读数据
	struct PeoInfo tmp = { 0 };//临时存储
	int ret = 0;
	char ch[10] = "0";
	fscanf(fp, "%s %s %s %s %s", ch, ch, ch, ch, ch);
	while (ret = (fscanf(fp, "%s %s %d %s %s", tmp.name, tmp.sex, 
		&(tmp.age), tmp.number, tmp.address)) >= 1)
	{
		if (pc->sz == pc->capacity)
			buyNewCapacity(pc);//扩容
		pc->data[pc->sz] = tmp;
		pc->sz++;
	}
	if (feof(fp))
		printf("\nEnd by EOF\n");
	else if (ferror(fp))
		printf("\nEnd by IO\n");

	//关闭文件
	fclose(fp);
	fp = NULL;
}
void ConSave(Con* pc)//通讯录信息保存
{
	assert(pc);
	FILE* fp = fopen("Contact.txt", "w");//打开文件
	if (NULL == fp)
	{
		perror("fopen::Contact.txt");
		return;
	}
	//写文件
	fprintf(fp, "%-10s\t%-5s\t%-s\t%-20s\t%-30s\n", "姓名", "性别", "年龄", "号码", "住址");	//写入标头信息
	int i = 0;
	for (i = 0; i < pc->sz; i++)
	{
		fprintf(fp, "%-10s\t%-5s\t%-d\t%-15s\t%-30s\n", pc->data[i].name, pc->data[i].sex,
			pc->data[i].age, pc->data[i].number, pc->data[i].address);
	}

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

🌱test.c? ? ? ? ?主函数源文件

#define _CRT_SECURE_NO_WARNINGS 1	
#include"Contact.h"

//第三代通讯录(文件版),刷新git提交

void menuByCon()
{
	printf("***************************************\n");
	printf("**********  0.退出  1.显示  **********\n");
	printf("**********  2.增加  3.删除  **********\n");
	printf("**********  4.查找  5.修改  **********\n");
	printf("**********  6.排序  7.数量  **********\n");
	printf("***************************************\n");
}
enum MenuByCon
{
	退出, 显示, 增加, 删除,
	查询, 修改, 排序, 数量
};
int main()
{
	Con c1;//创建了一个通讯录
	ConInit(&c1);

	int input = 1;
	while (input)
	{
		menuByCon();
		printf("请选择你的操作(只能输入数字):>");
		scanf("%d", &input);
		system("cls");
		switch (input)
		{
		case 退出:
			printf("退出通讯录系统!\n");
			ConSave(&c1);//保存通讯录
			ConDestroy(&c1);//销毁通讯录
			break;
		case 显示:
			ConDisplay(&c1);
			break;
		case 增加:
			ConAdd(&c1);
			break;
		case 删除:
			ConDelte(&c1);
			break;
		case 查询:
			ConFind(&c1);
			break;
		case 修改:
			ConRevise(&c1);
			break;
		case 排序:
			ConSort(&c1);
			break;
		case 数量:
			printf("当前通讯录中信息数为:%d\n", ConInfoNum(&c1));
			break;
		default:
			printf("选择错误,请重新选择!\n");
			break;
		}
	}
	return 0;
}

🌳总结

? 以上就是三个不同版本通讯录的分享,如果是学习的话,可以从静态版开始,逐步升级为文件版,后期可以尝试升级为数据库版;如果是为了课设做准备的话,可以直接看文件版,功能全面,运行稳定。总之,以上就是本期C语言课设分享的全部内容了,作为代码分享类文章,并没有进行太过详细的讲解,但代码量是可以得到保证的。

? 如果你觉得本文写的还不错的话,期待留下一个小小的赞👍,你的支持是我分享的最大动力!

? 如果本文有不足或错误的地方,随时欢迎指出,我会在第一时间改正

?

相关文章推荐

C语言进阶——自定义类型

C语言进阶——动态内存管理

C语言进阶——文件操作

  C++知识库 最新文章
【C++】友元、嵌套类、异常、RTTI、类型转换
通讯录的思路与实现(C语言)
C++PrimerPlus 第七章 函数-C++的编程模块(
Problem C: 算法9-9~9-12:平衡二叉树的基本
MSVC C++ UTF-8编程
C++进阶 多态原理
简单string类c++实现
我的年度总结
【C语言】以深厚地基筑伟岸高楼-基础篇(六
c语言常见错误合集
上一篇文章      下一篇文章      查看所有文章
加:2022-10-22 20:56:54  更:2022-10-22 20:57:10 
 
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁

360图书馆 购物 三丰科技 阅读网 日历 万年历 2024年5日历 -2024/5/19 3:41:46-

图片自动播放器
↓图片自动播放器↓
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
图片批量下载器
↓批量下载图片,美女图库↓
  网站联系: qq:121756557 email:121756557@qq.com  IT数码