1.要求
通讯录的数据格式如下:
struct DataType
{
int ID; //编号
char name[10]; //姓名
char ch; //性别
char phone[13]; //电话
char addr[31]; //地址
};
(1)实现通讯录的建立、增加、删除、修改、查询等功能;
(2)能够实现简单的菜单交互,即可以根据用户输入的命令,选择不同的操作;
(3)能够保存每次更新的数据;
(4)能够进行通讯录分类,比如班级类、好友类、黑名单等;
2.整体架构
整体分为三部分:
(1)链表类实现:文件DLinkList.h
类
链表类class DLinkList
的声明与实现;
(2)通讯录类实现:文件PhoneBook.h
和文件PhoneBook.cpp
PhoneBook.h
中声明数据格式struct DataType
和通讯录类class Phonebook
;
PhoneBook.cpp
中实现通讯录类class Phonebook
;
(3)主程序实现:文件Main.cpp
实现主函数int main()
以及需要用到的子函数。
3.关键部分
(1)建立链表类。
我选择双链表作为存储结构,命名为DLinkLIst
,并采用模板类以实现代码重用。
DLinkList
类代码如下:
//定义数据元素的结构
template<class T>
struct Node {
T data;
Node<T>* prior;
Node<T>* next;
};
//定义双链表类
template<class T>
class DLinkList {
private:
Node<T>* front;
public:
//无参构造函数
DLinkList();
//析构函数
~DLinkList();
//插入数据元素。i,插入位置;x,插入数据
void Insert(int i, T x);
//删除数据元素。i,删除位置;返回数据
T Delete(int i);
//按位查找数据元素。i,查找位置;返回指针
Node<T>* Get(int i);
//按值查找数据元素。x,查找值;返回第一次出现的位置
int Locate(T x);
//获取链表长度。返回长度
int GetLength();
//打印链表中所有数据元素
void PrintList();
//获取头指针。返回Node<T>*
Node<T>* GetFront();
};
//无参构造函数实现
template<class T>
DLinkList<T>::DLinkList() {
front = new Node<T>;
front->next = NULL;
front->prior = NULL;
}
//析构函数
template<class T>
DLinkList<T>::~DLinkList() {
Node<T>* p = front;
while (p) {
front = p;
p->prior = NULL;
p = p->next;
delete front;
}
}
//按位查找数据元素
template<class T>
Node<T>* DLinkList<T>::Get(int i) {
Node<T>* p = front->next;
int j = 1;
while (p && j != i) {
p = p->next;
j++;
}
return p;
}
//按值查找数据元素
template<class T>
int DLinkList<T>::Locate(T x) {
Node<T>* p = front->next;
int j = 1;
while (p) {
if (p->data == x) {
return j;
}
p = p->next;
j++;
}
return -1;
}
//插入数据元素
template<class T>
void DLinkList<T>::Insert(int i, T x) {
Node<T>* p = front;
if (i != 1) {
p = Get(i - 1);
}
if (p) {
Node<T>* q = new Node<T>;
q->data = x;
q->next = p->next;
q->prior = p;
p->next = q;
}
if (p->next->next) {
p->next->next->prior = p->next;
}
}
//删除数据元素
template<class T>
T DLinkList<T>::Delete(int i) {
Node<T>* p = front;
if (i != 1) {
p = Get(i - 1);
}
Node<T>* q = p->next;
p->next = q->next;
q->next = p;
T x = q->data;
delete q;
return x;
}
//获取链表长度
template<class T>
int DLinkList<T>::GetLength() {
int i = 0;
Node<T>* p = front->next;
while (p) {
p = p->next;
i++;
}
return i;
}
//打印链表中所有元素
template<class T>
void DLinkList<T>::PrintList() {
Node<T>* p = front->next;
int i = 1;
while (p) {
cout << i << " " << p->data;
i++;
p = p->next;
}
cout << endl;
}
//获取头指针
template<class T>
Node<T>* DLinkList<T>::GetFront() {
return front;
}
(2)通讯录类Phonebook
的声明与实现:
声明:
//通讯录类,用于操作DataType
class Phonebook {
private:
DataType* data; //联系人信息
char group[30]; //分组情况
//比较字符串。s1,s2,需要比较的两个字符串(字符串长度相同);l,字符串长度;返回比较结果,相同返回true,不同返回false
bool CompareString(char* s1, char* s2, int l);
//复制字符串。s1,目标字符串;s2,源字符串;l,字符串长度,两个字符串长度相同
void CopyString(char* s1, char* s2, int l);
public:
//无参构造函数
Phonebook();
//有参构造函数。d,联系人信息;g,分组
Phonebook(DataType& d, char g[]);
//有参构造函数。id,联系人ID;name,联系人姓名;ch,联系人性别;phone,联系人电话;addr,联系人地址;group,分组
Phonebook(int id, char name[], char ch, char phone[], char addr[], char group[]);
//复制构造函数。pb,复制Phonebook对象
Phonebook(Phonebook& pb);
//析构函数
~Phonebook();
//比较姓名。name,要查找的姓名;返回比较结果,相同返回true,不同返回false
bool CompareName(char* name);
//修改信息。id,新的ID;name,新的姓名;ch,新的性别;phone,新的电话;addr,新的地址;group,新的分组
void Modify(int id, char name[], char ch, char phone[], char addr[], char group[]);
//重载输出运算符。out,输出流;pb,Phonebook对象;返回写入信息的输出流
friend ostream& operator<<(ostream& out, Phonebook& pb);
//获取信息。d,要写入的DataType对象
void GetData(DataType& d);
//获取分组。g,要写入的char型数组
void GetGroup(char g[]);
//重载赋值运算符。
Phonebook operator=(Phonebook& pb);
};
实现:
//无参构造函数
Phonebook::Phonebook() {
data = new DataType;
data->ID = 0; //空联系人ID设为0
for (int i = 0; i < 10; i++) {
data->name[i] = '*'; //空联系人姓名设为*
}
data->ch = '*'; //空联系人性别设为*
for (int i = 0; i < 13; i++) {
data->phone[i] = '*'; //空联系人电话设为*
}
for (int i = 0; i < 31; i++) {
data->addr[i] = '*'; //空联系人地址设为*
}
for (int i = 0; i < 30; i++) {
group[i] = '*'; //空联系人分组设为*
}
}
//有参构造函数
Phonebook::Phonebook(DataType& d, char g[]) {
data = new DataType;
data->ID = d.ID;
CopyString(data->name, d.name, 10);
data->ch = d.ch;
CopyString(data->phone, d.phone, 13);
CopyString(data->addr, d.addr, 31);
CopyString(group, g, 30);
}
//有参构造函数
Phonebook::Phonebook(int id, char name[], char ch, char phone[], char addr[], char group[]) {
data = new DataType;
data->ID = id;
CopyString(data->name, name, 10);
data->ch = ch;
CopyString(data->phone, phone, 13);
CopyString(data->addr, addr, 31);
CopyString(this->group, group, 30);
}
//复制构造函数
Phonebook::Phonebook(Phonebook& pb) {
data = new DataType;
data->ID = pb.data->ID;
CopyString(data->name, pb.data->name, 10);
data->ch = pb.data->ch;
CopyString(data->phone, pb.data->phone, 13);
CopyString(data->addr, pb.data->addr, 31);
CopyString(group, pb.group, 30);
}
//析构函数
Phonebook::~Phonebook() {
delete data;
data = NULL;
}
//比较字符串
bool Phonebook::CompareString(char* s1, char* s2, int l) {
bool yes = true;
for (int i = 0; i < l; i++) {
if (s1[i] != s2[i]) {
yes = false;
break;
}
}
return yes;
}
//复制字符串
void Phonebook::CopyString(char* s1, char* s2, int l) {
for (int i = 0; i < l; i++) {
s1[i] = s2[i];
}
}
//重载输出运算符
ostream& operator<<(ostream& out, Phonebook& pb) {
out << "分组:" << pb.group << " ";
out << "ID:" << pb.data->ID << " ";
out << "姓名:" << pb.data->name << " ";
out << "性别:";
if (pb.data->ch == 'm') {
out << "男" << " ";
}
else if (pb.data->ch == 'f') {
out << "女" << " ";
}
else {
out << "?" << " ";
}
out << "电话:" << pb.data->phone << " ";
out << "地址:" << pb.data->addr << endl;
return out;
}
//比较姓名
bool Phonebook::CompareName(char* name) {
return CompareString(data->name, name, 10);
}
//修改联系人信息
void Phonebook::Modify(int id, char name[], char ch, char phone[], char addr[], char group[]) {
this->data->ID = id;
CopyString(this->data->name, name, 10);
this->data->ch = ch;
CopyString(this->data->phone, phone, 13);
CopyString(this->data->addr, addr, 31);
CopyString(this->group, group, 30);
}
//获取信息
void Phonebook::GetData(DataType& d) {
d.ID = data->ID;
CopyString(d.name, data->name, 10);
d.ch = data->ch;
CopyString(d.phone, data->phone, 13);
CopyString(d.addr, data->addr, 31);
}
//获取分组
void Phonebook::GetGroup(char g[]) {
CopyString(g, group, 30);
}
//重载赋值运算符
Phonebook Phonebook::operator=(Phonebook& pb) {
data = new DataType;
data->ID = pb.data->ID;
CopyString(data->name, pb.data->name, 10);
data->ch = pb.data->ch;
CopyString(data->phone, pb.data->phone, 13);
CopyString(data->addr, pb.data->addr, 31);
CopyString(group, pb.group, 30);
return *this;
}
(3)主程序实现
与用户的交互部分:
//主函数
int main() {
int out = 1;
DLinkList<Phonebook>pbook; //创建Phonebook链表用于存放联系人信息
ReadFromFile(pbook); //读取已有信息
cout << "----------欢迎使用通讯录-------------" << endl;
while (out) { //操作
cout << endl;
cout << "1.创建联系人" << endl;
cout << "2.删除联系人" << endl;
cout << "3.查找联系人" << endl;
cout << "4.修改联系人信息" << endl;
cout << "5.展示所有联系人信息" << endl;
cout << "6.退出" << endl;
cout << endl;
cout << "请输入操作序号:" << endl;
int num;
cin >> num; //读取操作
switch (num) //解析操作
{
case 1:
CreateContact(pbook); //创建联系人
break;
case 2:
DeleteContact(pbook); //删除联系人
break;
case 3:
SearchContact(pbook); //查找联系人
break;
case 4:
ModifyContact(pbook); //修改联系人信息
break;
case 5:
pbook.PrintList(); //打印所有联系人信息
break;
case 6:
out = 0; //退出程序
break;
default:
cout << "请输入正确的操作序号!" << endl;
break;
}
}
WriteToFile(pbook); //写入文件保存信息
return 0;
}
子函数CreateContact()
:
//创建联系人。pb,存放联系人信息的Phonebook链表。
void CreateContact(DLinkList<Phonebook>& pb) {
int id; //联系人ID
char name[10]; //联系人姓名
int s;
char ch; //联系人性别
char phone[13]; //联系人电话
char addr[31]; //联系人地址
char group[30]; //分组
cout << "请输入联系人ID(请输入数字):" << endl;
cin >> id;
cout << "请输入联系人姓名(输入的字符不要超过10):" << endl;
cin >> name;
cout << "请选择联系人性别:1.男 2.女" << endl;
cin >> s;
if (s == 1) {
ch = 'm';
}
else if (s == 2) {
ch = 'f';
}
else {
ch = 'n';
}
cout << "请输入联系人电话(输入的字符不要超过13):" << endl;
cin >> phone;
cout << "请输入联系人地址(输入的字符不要超过31):" << endl;
cin >> addr;
cout << "请输入联系人分组(输入的字符不要超过30):" << endl;
cin >> group;
Phonebook contact(id, name, ch, phone, addr, group); //创建Phonebook对象存放信息
pb.Insert(pb.GetLength() + 1, contact); //插入到通讯录中
}
子函数DeleteContact()
:
//删除联系人。pb,存放联系人信息的Phonebook链表
void DeleteContact(DLinkList<Phonebook>& pb) {
cout << "当前所有联系人信息:" << endl;
pb.PrintList();
cout << "请输入要删除联系人的序号:" << endl;
int i;
cin >> i;
cout << "确定删除?[y/n]" << endl;
char yes;
cin >> yes;
if (yes == 'y') {
pb.Delete(i); //删除联系人
}
}
子函数SearchContact()
:
//查找联系人,通过姓名查找。pb,存放联系人信息的Phonebook链表
void SearchContact(DLinkList<Phonebook>& pb) {
cout << "请输入要查找的联系人姓名:" << endl;
char name[10];
cin >> name;
Node<Phonebook>* p = pb.GetFront()->next;
while (p) {
if (p->data.CompareName(name)) {
break;
}
p = p->next;
}
if (p) {
cout << p->data;
}
else {
cout << "未查询到此联系人" << endl;
}
}
子函数ModifyContact()
:
//修改联系人。pb,存放联系人信息的Phonebook链表
void ModifyContact(DLinkList<Phonebook>& pb) {
cout << "当前所有联系人信息:" << endl;
pb.PrintList();
cout << "请输入需要修改的联系人序号:" << endl;
int i;
cin >> i;
int id;
char name[10];
int s;
char ch;
char phone[13];
char addr[31];
char group[30];
cout << "请输入新的信息:";
cout << "请输入联系人ID:" << endl;
cin >> id;
cout << "请输入联系人姓名(输入的字符不要超过10):" << endl;
cin >> name;
cout << "请选择联系人性别:1.男 2.女" << endl;
cin >> s;
if (s == 1) {
ch = 'm';
}
else if (s == 2) {
ch = 'f';
}
else {
ch = 'n';
}
cout << "请输入联系人电话(输入的字符不要超过13):" << endl;
cin >> phone;
cout << "请输入联系人地址(输入的字符不要超过31):" << endl;
cin >> addr;
cout << "请输入联系人分组(输入的字符不要超过30):" << endl;
cin >> group;
pb.Get(i)->data.Modify(id, name, ch, phone, addr, group);
}
子函数WriteToFile()
:
//信息存放,将所有信息存放到data.dat文件中。pb,存放联系人信息的Phonebook链表
void WriteToFile(DLinkList<Phonebook>& pb) {
Node<Phonebook>* p = pb.GetFront()->next;
ofstream outfile("data.dat", ios::out | ios::binary); //创建文件输出流,二进制方式写入
int length = pb.GetLength();
outfile.write((char*)&length, sizeof(length)); //写入联系人数量
while (p) {
DataType data;
p->data.GetData(data);
char group[30];
p->data.GetGroup(group);
outfile.write((char*)&data, sizeof(data)); //写入联系人信息
outfile.write(group, sizeof(group)); //写入联系人分组
p = p->next;
}
outfile.close(); //关闭输出流
}
子函数ReadFromFile()
:
//信息读取,读取data.dat文件,将信息存放到链表中。pb,存放联系人信息的Phonebook链表
void ReadFromFile(DLinkList<Phonebook>& pb) {
ifstream infile("data.dat", ios::in | ios::binary); //创建文件输入流,二进制方式读取
if (infile) {
int length;
infile.read((char*)&length, sizeof(length)); //读取联系人数量
for (int i = 0; i < length; i++) {
DataType data;
infile.read((char*)&data, sizeof(data)); //读取联系人信息
char group[30];
infile.read(group, sizeof(group)); //读取联系人分组
Phonebook p(data, group);
pb.Insert(i + 1, p); //保存到Phonebook链表中
}
}
infile.close(); //关闭输入流
}
4.中间过程中出现的问题
(1)DLinkList
类的Insert()
函数在插入到最后一个元素的后面时出现异常。
原因:逻辑错误,对Insert()
函数的实现出现错误;
解决:添加对最后一个元素的判断,防止使用空指针;
(2)Phonebook
类未声明复制构造函数和析构函数,虽然程序正常运行,但存在内存泄漏的风险。
解决:添加复制构造函数和析构函数;
(3)在添加了自定义的复制构造函数和析构函数后,程序在运行时多次出现读取到的指针指向未知内存空间。
原因:由于程序调用的是自定义析构函数和复制构造函数,使得程序运行时出现这种情形:一开始有一个对象a,并存储了数据在内存中的一片空间,在程序运行时,又定义了一个对象b,并且直接将a赋给了b,这时,两个对象存储的数据在同一片内存空间,之后在程序析构其中一个对象后,再次析构另一个对象,就会出现错误;
解决:重载赋值运算符;
(4)从控制台读取输入的信息时,当读取到“性别”信息时,无法存储到指定的变量中,并且影响了后续其他信息的存储。
原因:由于设置的将读取到的“性别”信息存储到一个char
型变量中,而程序实际读取到的是字符串;(具体原因有待探讨)
解决:通过更改用户的输入方式,即让用户输入数字,然后判断数字将信息分类存储;
(5)从二进制文件中读取出来的信息混乱。
原因:采用ofstream.write()
存储信息时,直接将对象转化为字符串写入,由于,该对象内部还有其他对象,这种存储方式会导致内部对象的信息丢失,故再次读取时,读到的信息是乱码;
解决:将对象内部的信息分成若干独立的内容存储,并且在读取时分别读取,再合并成一个对象;
(6)在通过对象调用其成员函数时,成员函数无法读取到对象存储的信息。
原因:在C++中成员函数实际上也是独立的函数,只不过与普通函数不同的是与类相关联,当未使用this指针时,是不能读取对象的数据的;
解决:使用this指针获取对象内部的数据。
5.不足
(1)程序的容错率低
当用户输入违法字符时,程序无法做出处理,会直接结束运行或是崩溃;
(2)界面不友好
控制台程序输出时会出现信息连续打印出来的情况,而上下行的内容容易看错,不易于用户找到期望的信息;
(3)操作复杂
对于一些操作,需要用户输入大量的内容,操作很不方便。如修改一个联系人信息,用户只想修改一个数据,但需要将所有信息重新输入一遍,很麻烦。
6.参考
https://blog.csdn.net/Move_now/article/details/78166621
http://c.biancheng.net/view/302.html