本项目使用C++语言,有过C语言基础的小伙伴也都能看得懂。项目基于动态顺序表实现通讯录项目。
项目需求:
(1)能够保存用户信息:姓名、电话、性别、住址等
(2)增加联系人信息
(3)删除指定联系人
(4)查找指定联系人
(5)修改指定联系人
(6)显示联系人信息
一、主界面
通讯录是一个非常适合新手写的小小项目,初学者通常以菜单的形式展示操作方法。为了使本次应用可以按照用户需求关闭程序,我们使用循环完成整体代码,但我们又不知道循环的具体次数,所以我们需要使用while或do...while循环来完成。为了使界面简洁,我们每次操作完成都会进行清屏操作。下面看一下运行结果:
代码实现:
#pragma once
#include <assert.h>
#include <string>
#include <iostream>
using namespace std;
#define LIST_INIT_SIZE 10
typedef struct Student {
string name;
string tel;
string sex;
string address;
}Stu;
typedef struct ContactList {
Stu* arr;
int size;//有效元素数Length
int capacity;//数组容量MaxSize
}Con;
/*-------------------上述代码放在头文件中-----------------------*/
void meau()
{
cout << " 欢迎使用Stark通讯录管理系统 " << endl;
cout << "*---------------------------------*" << endl;
cout << "0退出通讯录 1浏览通讯录 2查询联系人" << endl;
cout << "3添加联系人 4删除联系人 5修改联系人" << endl;
cout << "*---------------------------------*" << endl;
}
int main()
{
Contact sl;//创建工具类
Con mylist;//创建顺序表
sl.InitSqlist(&mylist);//初始化顺序表
while(1)
{
system("cls");
cout << "您一共有 ";cout<< sl.GetLength(mylist); cout << " 位联系人" << endl;
meau();//清屏-打印菜单
int way = 0; cin >> way;//选择功能
Stu data;//创建用户
int index;
switch (way)
{
case 0://退出程序
exit(0); break;
case 1://浏览联系人
cout << "序号\t姓名\t电话\t性别\t住址" << endl;
sl.PrintSqlist(mylist); break;
case 2://按照姓名或电话查询
cout << "请输入需要查询联系人的姓名/电话:" ;
cin >> data.name >> data.tel;
cout << "查询成功:" << endl;
sl.SelNode(&mylist, data);
break;
case 3://添加联系人
cout << "请依次输入联系人的姓名、电话、性别、住址" << endl;
cin >> data.name >> data.tel >> data.sex >> data.address;
sl.InsertData(&mylist, data);
cout << "添加成功" << endl;
break;
case 4://删除联系人
sl.PrintSqlist(mylist);
cout << "请选择需要删除的联系人的序号:" ;
cin >> index;
sl.DeleteNode(&mylist, index - 1);
break;
case 5://修改联系人信息
sl.PrintSqlist(mylist);
cout << "请选择需要修改的联系人的序号:" ;
cin >> index;
cout << "请依次输入修改后的姓名、电话、性别、住址" << endl;
cin >> data.name >> data.tel >> data.sex >> data.address;
sl.UpdateNode(&mylist, index-1, data);
break;
default:cout << "功能未实现..." << endl; break;
}
system("pause");
}
sl.DestroySqlist(&mylist);
return 0;
}
二、浏览联系人信息
前提操作:判断通讯录是否为空。以通讯录有效长度为临界值,通过for循环,依次遍历通讯录内容,将通讯录中存储的联系人信息依次输出,并使用制表符\t和换行来规范输出格式。
代码实现:
//打印通讯录
void PrintSqlist(Con s)
{
if (isEmpty(s))cout << "通讯录为空";
else
{
for (int i = 0; i < s.size; i++)
{
printf("第%d位: \t",i+1);
cout << (s.arr + i)->name << "\t" << (s.arr + i)->tel << "\t";
cout << (s.arr + i)->sex << "\t" << (s.arr + i)->address << endl;
}
}cout << endl;
}
三、查询联系人信息
前提操作:判断通讯录是否为空。通过循环判断第几个表节点信息符合,然后通过封装的getMsg方法将该位联系人信息输出。
代码实现:
void SelNode(Con* s, Stu data)
{
if (isEmpty(*s))cout << "通讯录为空" << endl;
else {
for (int i = 0; i < s->size; i++) {
if ((s->arr + i)->name == data.name)
{
getMsg(*s,i);
}
else if ((s->arr + i)->tel == data.tel)
{
getMsg(*s, i);
}
}
}cout << endl;
}
void getMsg(Con s, int index)
{
if (index<0 || index>s.size) cout<<"索引位溢出";
cout << (s.arr + index)->name << " " << (s.arr + index)->tel << " ";
cout << (s.arr + index)->sex << " " << (s.arr + index)->address << endl;
}
四、增加联系人信息
前提操作:如果通讯录是空表,初始化该表然后再插入数据。后续判断:有效长度与最大容量如果一致,说明通讯录满了,需要扩容。然后将传入的联系人信息依次存入。完成后将通讯录的有效长度+1。(本项目使用尾插,意味着添加时按照添加时间排序)
代码实现:
//插入数据(尾插)
void InsertData(Con* s, Stu data)
{
if (isEmpty(*s)) {
InitSqlist(s);//如果顺序表为空,则开辟一个顺序表
}
if ((s->size) == (s->capacity))
{
Stu* tmp = new Stu[s->capacity * 2];
assert(tmp);//判断指针是否为空
s->arr = tmp;
(s->capacity) *= 2;
}
s->arr[s->size].address = data.address;
s->arr[s->size].name = data.name;
s->arr[s->size].sex = data.sex;
s->arr[s->size].tel = data.tel;
(s->size)++;
}
五、删除指定联系人
按照序号删除联系人。所以在删除操作之前将浏览通讯录全部信息便于用户获取删除的序号。
代码如下:
void DeleteNode(Con* s, int index)
{
if (isEmpty(*s) || index > s->size || index < 0)
cout << "通讯录为空或索引超出通讯录范围" << endl;
else {
for (int i = index; i < (s->size); i++) {
Stu tmp = *(s->arr + i);
*(s->arr + i) = *(s->arr + i + 1);
*(s->arr + i + 1) = tmp;
}
(s->size)--;
cout << "删除成功" << endl;
}
}
六、修改联系人信息
修改操作同删除操作一样,陈列联系人,选择序号,按位删除
代码如下:
void UpdateNode(Con* s, int index, Stu data)
{
if (isEmpty(*s))cout << "通讯录为空" << endl;
else
{
*(s->arr + index) = data;
cout << "修改成功" << endl;
}
}
七、初始化与销毁
//初始化顺序表:给arr开辟10个空间
void InitSqlist(Con* s)
{
s->arr = new Stu[LIST_INIT_SIZE];
//s->arr = (Stu*)malloc(sizeof(Stu) * LIST_INIT_SIZE);
//if (isEmpty(*s))exit(OVERFLOW);
s->capacity = LIST_INIT_SIZE;
s->size = 0;//尚未存储数据
}
//销毁顺序表
void DestroySqlist(Con* s)
{
assert(s->arr);
s->capacity = s->size = 0;
s->arr = NULL;
delete[] s->arr;
cout << "销毁完毕" << endl;
}
由于结构体内含string类型数据,string有构造函数,开辟含该类型数据的结构体类型时需要使用new操作符开辟空间。置于原因,我们后续会讲到的,这里记住就行。 delete[] 操作符会调用析构函数,与new操作符搭配使用。malloc、free是函数,new、delete是操作符。建议最好不要任意搭配使用。
八、程序源码
//Contact.h文件
#pragma once
#include <assert.h>
#include <string>
#include <iostream>
using namespace std;
#define LIST_INIT_SIZE 10
typedef struct Student {
string name;
string tel;
string sex;
string address;
}Stu;
typedef struct ContactList {
Stu* arr;
int size;//有效元素数Length
int capacity;//数组容量MaxSize
}Con;
class Contact {
public:
//判断顺序表s是否为空
bool isEmpty(Con s)
{
return (s.arr == nullptr);
}
//获取顺序表s的长度
int GetLength(Con s)
{
return s.size;
}
void getMsg(Con s, int index)
{
if (index<0 || index>s.size) cout<<"索引位溢出";
cout << (s.arr + index)->name << " " << (s.arr + index)->tel << " ";
cout << (s.arr + index)->sex << " " << (s.arr + index)->address << endl;
}
//初始化顺序表:给arr开辟10个空间
void InitSqlist(Con* s)
{
s->arr = new Stu[LIST_INIT_SIZE];
//s->arr = (Stu*)malloc(sizeof(Stu) * LIST_INIT_SIZE);
//if (isEmpty(*s))exit(OVERFLOW);
s->capacity = LIST_INIT_SIZE;
s->size = 0;//尚未存储数据
}
//销毁顺序表
void DestroySqlist(Con* s)
{
assert(s->arr);
s->capacity = s->size = 0;
s->arr = NULL;
delete[] s->arr;
//free(s->arr);
cout << "销毁完毕" << endl;
}
//插入数据(尾插)
void InsertData(Con* s, Stu data)
{
if (isEmpty(*s)) {
InitSqlist(s);//如果顺序表为空,则开辟一个顺序表
}
if ((s->size) == (s->capacity))
{
Stu* tmp =new Stu[s->capacity*2];
//Stu* tmp = (Stu*)realloc(s->arr, (s->capacity * 2) * sizeof(Stu));
assert(tmp);//判断指针是否为空
s->arr = tmp;
(s->capacity) *= 2;
}
s->arr[s->size].address = data.address;
s->arr[s->size].name = data.name;
s->arr[s->size].sex = data.sex;
s->arr[s->size].tel = data.tel;
(s->size)++;
}
//修改顺序表第index位数据为data(按位改)
void UpdateNode(Con* s, int index, Stu data)
{
if (isEmpty(*s))cout << "通讯录为空" << endl;
else
{
*(s->arr + index) = data;
cout << "修改成功" << endl;
}
}
//删除顺序表第index位数据(按位删)
void DeleteNode(Con* s, int index)
{
if (isEmpty(*s) || index > s->size || index < 0)
cout << "通讯录为空或索引超出通讯录范围" << endl;
else {
for (int i = index; i < (s->size); i++) {
Stu tmp = *(s->arr + i);
*(s->arr + i) = *(s->arr + i + 1);
*(s->arr + i + 1) = tmp;
}
(s->size)--;
cout << "删除成功" << endl;
}
}
//查找顺序表第index位数据(按位查找)
void SelNode(Con s, int index)
{
if (isEmpty(s))cout << "通讯录为空" << endl;
else getMsg(s,index);
}
//在顺序表中查找数据data,方法重载(按姓名值/电话值查找)
void SelNode(Con* s, Stu data)
{
if (isEmpty(*s))cout << "通讯录为空" << endl;
else {
for (int i = 0; i < s->size; i++) {
if ((s->arr + i)->name == data.name)
{
getMsg(*s,i);
}
else if ((s->arr + i)->tel == data.tel)
{
getMsg(*s, i);
}
}
}cout << endl;
}
//打印顺序表
void PrintSqlist(Con s)
{
if (isEmpty(s))cout << "通讯录为空";
else
{
for (int i = 0; i < s.size; i++)
{
printf("第%d位: \t",i+1);
cout << (s.arr + i)->name << "\t" << (s.arr + i)->tel << "\t";
cout << (s.arr + i)->sex << "\t" << (s.arr + i)->address << endl;
}
}cout << endl;
}
};
//Contact.cpp文件
#include "Contact.h"
#include <string>
#include <iostream>
using namespace std;
void meau()
{
cout << " 欢迎使用Stark通讯录管理系统 " << endl;
cout << "*---------------------------------*" << endl;
cout << "0退出通讯录 1浏览通讯录 2查询联系人" << endl;
cout << "3添加联系人 4删除联系人 5修改联系人" << endl;
cout << "*---------------------------------*" << endl;
}
int main()
{
Contact sl;//创建工具类
Con mylist;//创建链表
sl.InitSqlist(&mylist);
while(1)
{
system("cls");
cout << "您一共有 ";cout<< sl.GetLength(mylist); cout << " 位联系人" << endl;
meau();//清屏-打印菜单
int way = 0; cin >> way;//选择功能
Stu data;//创建用户
int index;
switch (way)
{
case 0:
exit(0); break;
case 1:
cout << "序号\t姓名\t电话\t性别\t住址" << endl;
sl.PrintSqlist(mylist); break;
case 2:
cout << "请输入需要查询联系人的姓名/电话:" ;
cin >> data.name >> data.tel;
cout << "查询成功:" << endl;
sl.SelNode(&mylist, data);
break;
case 3:
cout << "请依次输入联系人的姓名、电话、性别、住址" << endl;
cin >> data.name >> data.tel >> data.sex >> data.address;
sl.InsertData(&mylist, data);
cout << "添加成功" << endl;
break;
case 4:
sl.PrintSqlist(mylist);
cout << "请选择需要删除的联系人的序号:" ;
cin >> index;
sl.DeleteNode(&mylist, index - 1);
break;
case 5:
sl.PrintSqlist(mylist);
cout << "请选择需要修改的联系人的序号:" ;
cin >> index;
cout << "请依次输入修改后的姓名、电话、性别、住址" << endl;
cin >> data.name >> data.tel >> data.sex >> data.address;
sl.UpdateNode(&mylist, index-1, data);
break;
default:cout << "功能未实现..." << endl; break;
}
system("pause");
}
sl.DestroySqlist(&mylist);
return 0;
}
思考1.用静态顺序表如何实现功能
思考2.如何保证程序结束后,历史 通讯录信息不会丢失
问题1.中间/头部的插入删除,时间复杂度为O(n)
问题2.扩容需要申请空间、拷贝数据、释放旧空间。会有不小的消耗。
问题3.扩容时一般是二倍增长,假如当前容量为100,需要扩容插入数据,此时二倍扩容使容量变为200,但我们只插入了5个数据,剩余的95个空间就这样浪费掉了。
推荐顺序表的两个算法:
经典算法OJ1:27. 移除元素 - 力扣(LeetCode)
经典算法OJ2:88. 合并两个有序数组 - 力扣(LeetCode)
感谢大家!