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

(C语言)文本动态通讯录(动态通讯录升级版)(C语言小项目)

 1.首先是头文件:
//头文件
//contact.h

//防止头文件被重复包含
#pragma once
//定义符号常亮,方便维护和修改
//联系人基本信息容量
#define NAME_MAX 20
#define AGE_MAX 5
#define SEX_MAX 5
#define TELE_MAX 15
#define ADDR_MAX 30
//联系人最大容量
#define MAX 1

//定义联系人结构体
struct PeopleInfo
{
	char name[NAME_MAX];
	char age[AGE_MAX];
	char sex[SEX_MAX];
	char tele[TELE_MAX];
	char addr[ADDR_MAX];
};
//定义通讯录结构体
struct Contact
{
	struct PeopleInfo* data;
	int sz;
	int capacity;
};

//声明函数
void clear_screen();
void AddContact(struct Contact* con);
void DelContact(struct Contact* con);
void ShowContact(struct Contact* con);
int FindContact(const struct Contact* con, char name[]);
void InitContact(struct Contact* con);
void menu();
void ModifyContact(struct Contact* con);
void SearchContact(struct Contact* con);
void SortContact(struct Contact* con);
void ClearContact(struct Contact* con);
void ClearCapContact(struct Contact* con);
void ReadTxtContact(struct Contact* con);
void WriteTxtContact(struct Contact* con);
int by_name_cmp2(const void* x1, const void* x2);
int by_name_cmp1(const void* x1, const void* x2);
2. 然后是功能函数contact.c文件
//功能函数文件
//contact.c	

#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include "contact.h"

//定义菜单函数
void menu()
{
	printf("*********************************************\n");
	printf("******** 1.添加        2.删除  **************\n");
	printf("******** 3.查询        4.修改  **************\n");
	printf("******** 5.查看        6.排序  **************\n");
	printf("******** 7.清空        0.退出  **************\n");
	printf("*********************************************\n");
}

//定义清屏函数
//清屏操作
void clear_screen() {
	//判断是否为Windows系统
#ifdef _WIN32
	system("cls");
	//其他系统
#else
	system("clear");
#endif
}

//读入文件
void ReadTxtContact(struct Contact* con) {
	FILE* fp = fopen("DynamicAddressBook.txt", "rb");
	if (fp == NULL) {
		return; // 文件不存在时不报错
	}

	struct PeopleInfo tmp;
	while (fread(&tmp, sizeof(struct PeopleInfo), 1, fp) == 1) {
		// 动态扩容检查
		if (con->sz >= con->capacity) {
			int newCapacity = (con->capacity == 0) ? 1 : con->capacity * 2;
			struct PeopleInfo* cap = (struct PeopleInfo*)realloc(con->data, newCapacity * sizeof(struct PeopleInfo));
			if (cap == NULL) {
				printf("文件数据加载失败:内存不足\n");
				break;
			}
			con->data = cap;
			con->capacity = newCapacity;
		}
		con->data[con->sz] = tmp;
		con->sz++;
	}

	fclose(fp);
}

//写入文件
void WriteTxtContact(struct Contact* con) {
	FILE* fp = fopen("DynamicAddressBook.txt", "wb");
	if (fp == NULL) {
		printf("打开文件失败\n");
		perror("WriteTxtContact: fopen");
	}
	int i = 0;
	for (i=0; i < con->sz; i++) {
		fwrite((con->data + i), sizeof(struct PeopleInfo), 1, fp);
	}
	fclose(fp);
	fp = NULL;
}

//初始化通讯录函数
void InitContact(struct Contact* con) {
	con->data = NULL;
	con->sz = 0;
	con->capacity = 0;

	// 尝试加载文件数据
	ReadTxtContact(con);

	// 若文件无数据,分配初始内存
	if (con->capacity == 0) {
		con->data = (struct PeopleInfo*)calloc(MAX, sizeof(struct PeopleInfo));
		if (con->data == NULL) {
			printf("内存分配失败!\n");
			exit(EXIT_FAILURE);
		}
		con->capacity = MAX;
	}
}

//添加联系人函数
void AddContact(struct Contact* con) {
	//检查通讯录是否溢出
	if (con->sz == con->capacity) {
		int newCapacity = con->capacity * 2;
		struct PeopleInfo* cap = (struct PeopleInfo*)realloc(con->data, newCapacity * sizeof(struct PeopleInfo));
		if (cap != NULL) {
			con->data = cap;
			con->capacity = newCapacity;
		}
		else
		{
			//扩容失败
			return;
		}
	}
	printf("请输入姓名:");
	scanf("%s", con->data[con->sz].name);
	printf("请输入年龄:");
	scanf("%s", con->data[con->sz].age);
	printf("请输入性别:");
	scanf("%s", con->data[con->sz].sex);
	printf("请输入电话:");
	scanf("%s", con->data[con->sz].tele);
	printf("请输入地址:");
	scanf("%s", con->data[con->sz].addr);
	printf("添加成功!\n");
	(con->sz)++;
}

//查找当前联系人函数
int  FindContact(const struct Contact* con, char name[]) {
	for (int i = 0; i < con->sz; i++) {
		//利用比较函数strcmp判断姓名是否相等
		if (strcmp(con->data[i].name, name) == 0) {
			return i;
		}
	}
	printf("用户不存在!\n");
	return -1;
}

//删除联系人函数
void DelContact(struct Contact* con) {
	if (con->sz == 0) {
		printf("通讯录为空!\n");
	}
	else
	{
		printf("请输入要删除的联系人姓名:\n");
		char name[NAME_MAX];
		scanf("%s", name);
		int ret = FindContact(con, name);
		if (ret != -1) {
			printf("删除成功!\n");
			for (int i = ret; i < con->sz - 1; i++) {
				con->data[i] = con->data[i + 1];
			}
			(con->sz)--;
		}
	}
}

//查询当前通讯录
void ShowContact(struct Contact* con) {
	if (con->sz == 0) {
		printf("该通讯录为空\n");
	}
	else {
		printf("%-15s\t%-5s\t%-8s\t%-15s\t%-30s\n", "姓名", "年龄", "性别", "电话", "地址");

		for (int i = 0; i < con->sz; i++) {
			printf("%-15s\t%-5s\t%-8s\t%-15s\t%-30s\n", con->data[i].name,
				con->data[i].age, con->data[i].sex, con->data[i].tele, con->data[i].addr);
		}
	}
}

//查询联系人函数
void SearchContact(struct Contact* con) {
	if (con->sz == 0) {
		printf("当前通讯录为空\n");
	}
	else
	{
		printf("请输入要查询的联系人姓名:\n");
		char name[NAME_MAX];
		scanf("%s", name);
		int ret = FindContact(con, name);
		if (ret != -1) {
			printf("查询成功!\n");
			printf("%-15s\t%-5s\t%-8s\t%-15s\t%-30s\n", "姓名", "年龄", "性别", "电话", "地址");
			printf("%-15s\t%-5s\t%-8s\t%-15s\t%-30s\n", con->data[ret].name,
				con->data[ret].age, con->data[ret].sex, con->data[ret].tele, con->data[ret].addr);
		}
	}
}

//修改联系人函数
void ModifyContact(struct Contact* con) {
	if (con->sz == 0) {
		printf("通讯录为空\n");
	}
	else
	{
		printf("请输入要修改的联系人姓名:\n");
		char name[NAME_MAX];
		scanf("%s", name);
		int Mod_0 = 2;
		int ret = FindContact(con, name);
		if (ret != -1) {
			printf("当前联系人数据!\n");
			printf("%-15s\t%-5s\t%-8s\t%-15s\t%-30s\n", "姓名", "年龄", "性别", "电话", "地址");
			printf("%-15s\t%-5s\t%-8s\t%-15s\t%-30s\n", con->data[ret].name,
				con->data[ret].age, con->data[ret].sex, con->data[ret].tele, con->data[ret].addr);
			printf("是否确定修改??(按1继续,按0退出)\n");
			while (1) {
				if (scanf("%d", &Mod_0) != 1) {
					printf("输入不合法,请重新输入");
				}
				if (Mod_0 == 1) {
					printf("请输入姓名:");
					scanf("%s", con->data[ret].name);
					printf("请输入年龄:");
					scanf("%s", con->data[ret].age);
					printf("请输入性别:");
					scanf("%s", con->data[ret].sex);
					printf("请输入电话:");
					scanf("%s", con->data[ret].tele);
					printf("请输入地址:");
					scanf("%s", con->data[ret].addr);
					printf("修改成功!\n");
					break;
				}
				else if (Mod_0 == 0) {
					break;
				}
			}
		}
	}
}


//比较函数
int by_name_cmp1(const void* x1, const void* x2) {
	return strcmp(((struct PeopleInfo*)x1)->name, ((struct PeopleInfo*)x2)->name);
}
int by_name_cmp2(const void* x1, const void* x2) {
	return strcmp(((struct PeopleInfo*)x2)->name, ((struct PeopleInfo*)x1)->name);
}
//排序联系人函数
void SortContact(struct Contact* con) {
	if (con->sz == 0) {
		printf("通讯录为空\n");
	}
	else {
		int Sort_0 = 2;
		printf("请选择排序方式(按1升序,按0降序):\n");
		while (1) {
			if (scanf("%d", &Sort_0) != 1) {
				printf("输入不合法,请重新输入");
			}
			if (Sort_0 == 1) {
				qsort(con->data, con->sz, sizeof(struct PeopleInfo), by_name_cmp1);
				printf("升序排序已完成\n");
				break;
			}
			else if (Sort_0 == 0)
			{
				qsort(con->data, con->sz, sizeof(struct PeopleInfo), by_name_cmp2);
				printf("降序排序已完成\n");
				break;
			}
		}
	}
}
//清空通讯录函数
void ClearContact(struct Contact* con) {
	if (con->sz == 0) {
		printf("通讯录为空\n");
	}
	else
	{
		con->sz = 0;
		memset(con->data, 0, MAX * sizeof(struct PeopleInfo));
		printf("当前通讯录已经清空\n");
	}
}

//释放空间函数
void ClearCapContact(struct Contact* con) {
	free(con->data);
	con->data = NULL;
	con->sz = 0;
	con->capacity = 0;
}
3.最后是主程序test.c文件:
//主程序文件
//test.c

#include <stdio.h>
#include <stdlib.h>
#include "contact.h"

//枚举条件选择定义(搭配switch使用)
enum Option
{
	EXIT,//0,对应退出通讯录
	ADD,//1,对应添加联系人
	DEL,//2,对应删除联系人
	SEARCH,//3,对应查询联系人
	MODIFY,//4,对应修改联系人
	SHOW,//5,对应查看通讯录
	SORT,//6,对应排序通讯录
	CLEAR,//7,对应清空通讯录
};

//主函数
int main()
{
	int input = 0;
	int menu_0 = 0;
	//创建通讯录
	struct Contact con;
	//调用函数初始化通讯录
	InitContact(&con);//传递参数地址

	do
	{
		//打印菜单
		while (1) {
			printf("************按1继续************\n");
			if (scanf("%d", &menu_0) != 1 && menu_0 != 1) {
				printf("输入不合法,请按1继续\n");
				return 1;
			}
			clear_screen();
			if (menu_0 == 1)
			{
				menu();
				break;
			}
		}
		printf("请选择对应模式(0-7):\n");
		if (scanf("%d", &input) != 1 || input < 0 || input > 7) {
			printf("输入不合法,请输入整数0-7\n");
			return 1;
		}
		switch (input)
		{
		case EXIT: {
			clear_screen();
			WriteTxtContact(&con); // 先保存数据
			ClearCapContact(&con); // 再释放内存
			printf("退出通讯录!\n");
			break;
		}
		case ADD: {
			clear_screen();
			AddContact(&con);
			break;
		}
		case DEL: {
			clear_screen();
			DelContact(&con);
			break;
		}
		case SEARCH: {
			clear_screen();
			SearchContact(&con);
			break;
		}
		case SHOW: {
			clear_screen();
			ShowContact(&con);
			break;
		}
		case MODIFY: {
			clear_screen();
			ModifyContact(&con);
			break;
		}
		case SORT: {
			clear_screen();
			SortContact(&con);
			break;
		}
		case CLEAR: {
			clear_screen();
			ClearContact(&con);
			break;
		}
		default:
			break;
		}

	} while (input);
	return 0;
}

整个项目只有三个文件,头文件和两个源代码

我们之前的通讯录在程序关闭后,联系人信息就销毁了,那我们如果想存储联系人信息该怎么办? 

当然是进行文件储存啦:

在 C 语言中,数据文件是用于长期存储数据的载体,程序可以通过文件操作(读写)将数据持久化保存到磁盘中。文件操作是 C 语言中重要的功能,广泛应用于数据记录、配置存储、日志生成等场景。


一、文件的两种类型

C 语言中,文件分为 文本文件 和 二进制文件

  1. 文本文件

    • 内容是人类可读的字符(如 .txt)。

    • 数据以字符形式存储(例如数字 123 存储为字符 '1''2''3')。

    • 适合存储简单的配置或日志。

  2. 二进制文件

    • 内容是二进制字节流(如 .dat、图片、音频)。

    • 数据按内存中的原始二进制形式存储。

    • 适合存储结构体、数组等复杂数据。


二、文件操作的核心步骤

所有文件操作都遵循以下流程:

  1. 打开文件 → 2. 读写文件 → 3. 关闭文件


三、如何打开文件?

使用 fopen() 函数打开文件,语法:

FILE* fopen(const char* filename, const char* mode);
  • 参数

    • filename:文件路径(如 "data.txt")。

    • mode:打开模式(见下表)。

常用打开模式
模式说明(文本文件)说明(二进制文件)
"r"只读(文件必须存在)"rb"
"w"写入(覆盖创建)"wb"
"a"追加(末尾添加)"ab"
"r+"读写(文件必须存在)"r+b"
"w+"读写(覆盖创建)"w+b"
"a+"读写(末尾添加)"a+b"
示例:打开文件
#include <stdio.h>

int main() {
    FILE* fp = fopen("data.txt", "w"); // 以写入模式打开文本文件
    if (fp == NULL) {
        printf("文件打开失败!\n");
        return 1;
    }
    // 其他操作...
    fclose(fp); // 关闭文件
    return 0;
}

四、如何读写文件?

1. 文本文件操作
  • 写入文本文件

    fprintf(fp, "内容"); // 类似 printf,但写入到文件
    fputs("内容", fp);    // 直接写入字符串
  • 读取文本文件

    char buffer[100];
    fscanf(fp, "%s", buffer); // 类似 scanf,从文件读取
    fgets(buffer, 100, fp);   // 读取一行(最多 99 字符)
2. 二进制文件操作
  • 写入二进制文件

    struct Student stu = { "Alice", 20 };
    fwrite(&stu, sizeof(struct Student), 1, fp); // 写入结构体
  • 读取二进制文件

    struct Student stu;
    fread(&stu, sizeof(struct Student), 1, fp); // 读取结构体

五、完整示例

示例 1:写入文本文件
#include <stdio.h>

int main() {
    FILE* fp = fopen("data.txt", "w");
    if (fp == NULL) return 1;

    fprintf(fp, "Hello, World!\n");
    fprintf(fp, "数字: %d\n", 123);

    fclose(fp);
    return 0;
}
示例 2:读取文本文件
#include <stdio.h>

int main() {
    FILE* fp = fopen("data.txt", "r");
    if (fp == NULL) return 1;

    char buffer[100];
    while (fgets(buffer, 100, fp) != NULL) {
        printf("%s", buffer);
    }

    fclose(fp);
    return 0;
}
示例 3:二进制文件操作
#include <stdio.h>

struct Student {
    char name[20];
    int age;
};

int main() {
    // 写入二进制文件
    FILE* fp = fopen("students.dat", "wb");
    struct Student stu1 = { "Alice", 20 };
    fwrite(&stu1, sizeof(struct Student), 1, fp);
    fclose(fp);

    // 读取二进制文件
    fp = fopen("students.dat", "rb");
    struct Student stu2;
    fread(&stu2, sizeof(struct Student), 1, fp);
    printf("姓名: %s, 年龄: %d\n", stu2.name, stu2.age);
    fclose(fp);

    return 0;
}

六、关键注意事项

  1. 检查文件是否打开成功

    if (fp == NULL) {
        // 处理错误
    }
  2. 始终关闭文件

    • 使用 fclose(fp) 避免内存泄漏和文件损坏。

  3. 区分文本和二进制模式

    • 在 Windows 系统中,文本模式会自动转换换行符(\r\n ↔ \n),二进制模式则不会。

  4. 文件指针的位置

    • 使用 fseek(fp, offset, origin) 可以移动文件指针的位置(如随机访问)。

上面就是C语言中文件的一些知识点

还有一些关键的知识点:

在C语言中,将文件指针 fp 设置为 NULL 是一种重要的编程实践,尤其是在文件操作完成后。以下是详细的解释:


1. 代码示例

fclose(fp);   // 关闭文件
fp = NULL;    // 显式置空指针

2. 核心作用

① 防止悬挂指针(Dangling Pointer)
  • 问题fclose(fp) 会释放文件资源,但指针 fp 仍保留原内存地址(此时指向无效内存)。

  • 风险:若后续误操作(如再次 fclose(fp) 或 fread),会引发未定义行为(程序崩溃或数据损坏)。

  • 解决方案fp = NULL 将指针标记为无效,任何对其的误操作将直接暴露问题。

② 提高代码健壮性
  • 安全访问:对 NULL 指针的操作(如 fclose(NULL))通常会立即导致错误(如段错误),而非静默失败。

  • 防御性编程:强制开发者在使用指针前检查有效性。


3. 实际意义

① 避免双重释放(Double Free)
fclose(fp);    // 第一次释放
// ...其他代码...
fclose(fp);    // 错误!fp仍指向已释放的内存(悬挂指针)
  • 未置空:第二次 fclose(fp) 会导致未定义行为(程序可能崩溃)。

  • 置空后fp = NULL 使得第二次 fclose(fp) 变为 fclose(NULL),C标准规定此操作安全(无操作)。

② 明确指针生命周期
  • 代码可读性fp = NULL 明确告知后续代码“此指针已失效”。

  • 调试友好:调试时若发现 fp 为 NULL,可快速定位到指针未被正确初始化或已释放。


4. 底层原理

① 指针与内存管理
  • fopen 返回的指针指向操作系统管理的文件资源结构体。

  • fclose 释放资源后,指针变为悬挂指针,但其值不变(仍指向原地址,但该地址已无效)。

② NULL 的语义
  • NULL 是标准定义的无效指针值(通常是 (void*)0)。

  • 对 NULL 指针的操作(如访问或释放)会立即触发错误,而非操作随机内存。


5. 正确实践

① 关闭文件后立即置空
FILE* fp = fopen("data.txt", "r");
if (fp) {
    // 文件操作...
    fclose(fp);
    fp = NULL;  // 显式置空
}
② 函数返回前清理指针
void process_file() {
    FILE* fp = fopen("data.txt", "r");
    if (!fp) {
        perror("fopen");
        return;
    }
    // ...操作文件...
    fclose(fp);
    fp = NULL;  // 确保指针离开作用域前无效
}
③ 结合作用域限制
{
    FILE* fp = fopen("data.txt", "r");  // 限制在代码块内
    if (fp) {
        // 操作文件...
        fclose(fp);
        fp = NULL;
    }
} // fp在此处自动失效(离开作用域)

6. 常见问题解答

Q1:fclose(NULL) 安全吗?
  • C标准规定fclose(NULL) 是未定义行为(UB),但大多数现代系统(如Linux/Windows)会静默忽略。

  • 实际建议:仍应避免 fclose(NULL),但在防御性编程中,置空指针可减少风险。

Q2:为何不依赖作用域自动回收?
  • 全局/静态指针:若指针为全局变量,其生命周期不限于作用域,必须手动置空。

  • 长期维护:显式置空能防止未来代码修改时引入错误。

Q3:是否所有指针都需要置空?
  • 动态内存指针free(ptr) 后也应置空(ptr = NULL)。

  • 函数内局部指针:若指针即将离开作用域(如函数结束),可不置空,但显式置空仍是好习惯。


7. 对比其他语言

语言文件指针管理特点
C手动置空(fp = NULL需开发者显式管理,避免悬挂指针
C++RAII(析构函数自动关闭)通过智能指针或类自动管理
Java垃圾回收(GC)无显式指针操作,自动回收资源
Pythonwith 语句自动关闭上下文管理器保证资源释放

总结

将文件指针 fp 置为 NULL 是C语言中关键的防御性编程技术,其核心价值在于:

  1. 防止悬挂指针引发的未定义行为

  2. 提高代码可读性和可维护性

  3. 强制开发者显式管理资源生命周期

 

明天会带大家做一个新的项目:学生班级信息表(和今天的很相似),用来巩固一下通讯录的知识 

源代码在下面呦:

双叶/文本通讯录 

注:该代码是本人自己所写,可能不够好,不够简便,欢迎大家指出我的不足之处。如果遇见看不懂的地方,可以在评论区打出来,进行讨论,或者联系我。上述内容全是我自己理解的,如果你有别的想法,或者认为我的理解不对,欢迎指出!!!如果可以,可以点一个免费的赞支持一下吗?谢谢各位彦祖亦菲!!!!! 

相关文章:

  • macOS 15 通过 MacPorts 安装 PHP 7 构建错误找不到符号在 dns.o 中解决方法
  • 使用 rsync 进行服务器文件同步与优化
  • STM32基础教程——输入捕获模式测量PWM频率
  • SD(Stable Diffusion)模型学习图谱
  • 视频生成的测试时Scaling时刻!清华开源Video-T1,无需重新训练让性能飙升
  • 内网YUM源搭建手册(Internal Network yum Source Construction Manual)
  • c++ primer 阅读手记 第六章
  • RCE——回调后门
  • 【ADC测试】在ADC马密度的方式测试INL和DNL
  • 蓝桥杯python编程每日刷题 day 20
  • postman测试调用WebService时不会自动添加命名空间
  • 交换机与路由器的区别:深入解析
  • nginx优化(持续更新!!!)
  • cv图像分割
  • Python正则表达式(二)
  • 从零开始跑通3DGS教程:介绍
  • Java + LangChain 实战入门,开发大语言模型应用!
  • 【实战】解决图片 Hover 抖动问题的完整指南
  • Qt:QWebEngineView显示网页失败
  • css100个问题
  • 墙绘做网站靠谱不/seo搜索引擎优化期末考试
  • 做易拉宝的素材网站/windows优化大师免费
  • 四川省建设厅职业注册中心网站/关键词优化推广排名软件
  • 企业网站群建设的原因/软文案例200字
  • 网站建设软件开发工作室整站模板/百度网盘人工客服电话
  • 欧米茄官方网站/网站seo诊断分析报告