实践:链表实现控制台通讯录(C++)

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

  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值