数据结构 --链表
前言
今天把链表重新用c++写了一遍,首先单纯的写一个链表并不困难,无非是定义一个结构体ListNode,设置变量data和下一个指针的地址next,然后完成增删查改的操作,需要注意的是在删除节点的时候记得先保存当前需要删除的节点的下一个节点,然后再删除当前节点并链接节点,否则会导致找不到删除的节点的下一个节点,无法链接。释放链表的时候需要遍历析构,不能直接简单将第一个节点释放而不管剩下的节点了。
废了我一番时间的是在如何将链表作为一个新容器加入进我前几篇写的通讯录这个项目中。一开始我想的应该不是很困难,应该和顺序表一样只要调用对应的接口就能正确完成任务栏,事与愿违,在调用链表的find的返回值是链表的当前节点指针,并不是数据的地址T*,和一开始Contact类的公共接口有出入:
T* find(const T& x)//查找
{T* pos = cont->Find(x);if (pos != nullptr){cout << "Sucessly Find : " << endl;return pos;}else{cout << "No Find" << endl;return nullptr;}
}
当然可以直接去特化一个ListNode*参数的find,但是这违背了封装上层的本意,应该修改下层对应上层的接口而不是修改上层,而且此时Contact为了适配不同的容器我加了两个参数模板,此时Contact<K<T>>,而这个Contact是并不知道它需要接受的是什么容器,这正是c++封装的一个大特征,我只需要提供调用上层的接口就能实现下层功能。我思考过用auto推导pos的返回值,但是nullptr和ListNode的类型不相符,所以就放弃了。那么还有什么方法可以自己去推导返回值呢?搜索了一下发现一个叫做traist的操作,即特性萃取。Traits 是一种编译期类型推导技术,用于根据输入类型关联或提取其他类型或值。这里的目标是:给定一个容器类型如(SqList<T>或者List),自动推导出它的查找结果类型,如(T*或者ListNode<T>*),这就能够解决了上诉的问题了。
使用方法:
然后将返回值改成FindResult就好了,这里其实也是类模板的实现,只不过这里是用类型实例化出不同的类,然后实例不同的FindResult,没有具体的类其他的操作。
为什么需要 Traits?
解耦容器与算法:算法(如查找)不需要知道容器的具体实现,只需通过 Traits 获取所需类型。
通用代码:同一套代码可以适配多种容器类型,只需为每种容器特化 FindResultType
。
然后第二个地方是修改这个地方的内容:
void change(const string& name)//修改
{auto pos = find(name);int size = cont->size();if(pos){//cin >> *pos;错误cout << "Sucessly Change" << endl;}else{cout << "Change False, NO Person" << endl;}
}
由于链表中的Find是返回ListNode*的指针,和顺序表以及内置类型直接返回储存数据的地址不同,对ListNode*指针解引用并不能得到数据。有的人会说了直接重载ListNode的解引用不就好了,确实我也尝试过去重载,然后返回*pos,但是这里不能直接使用*pos返回,因为pos假如是一个ListNode<T>*指针并且重载了解引用操作,需要解引用两次才能找到data数据,但是对于自定义类型来说这个操作是不允许的,而为了保证上层代码的统一性就没有进行特化,这里我使用的是仿函数的方式来解决这个问题。
所以我将函数模板又增加了一个类型,增加了一个默认的仿函数针对内置类型和直接能够返回T*的容器类型,对于自定义类型增加了特殊的仿函数,例如链表或者map这些有奇效。
template<typename T>
struct KeyOfT
{T& operator()(T* t) const //当数据内容是int,char等内置类型使用默认的仿函数{return *t;}
};template<typename T>
struct LOT//仿函数获取链表节点数据
{T& operator()(ListNode<T>* pos){return pos->_data;}
};
所以目前的Contact类被拓展成了这样,并且我PersonInfo作为单独的文件方便管理。
#pragma once
#include<string.h>
#include"traist.h"template<typename T>
struct KeyOfT
{T& operator()(T* t) const //当数据内容是int,char等内置类型使用默认的仿函数{return *t;}
};template <typename T, template<typename> class K, typename F= KeyOfT<T>>//将储存数据的容器作为模板嵌套
class Contact
{ //traist操作,确保传入不同类型能够返回对应的数据类型using ContainerType = K<T>;using FindResult = typename FindResultType<ContainerType>::type;public:Contact():cont(new K<T>())//指针:不会自动构造,必须用new{}~Contact(){delete cont;cont = nullptr;}void push()//增加{T data;cin >> data;cont->PushBack(data);}void push(T data){cont->PushBack(data);}void print()//打印{cont->Print();}void pop(T x){auto pos = cont->Find(x);int size = cont->size();if (pos != nullptr){cout << "Sucessly Erase" << endl;cont->Erase(pos);}else{cout << "Erase False, NO Person" << endl;}}void pop(const string& name)//删除{auto pos =cont->Find(name);int size = cont->size();if (pos != nullptr){cout << "Sucessly Erase" << endl;cont->Erase(pos);}else{cout << "Erase False, NO Person" << endl;}}FindResult find(const string& name)//查找{FindResult pos = cont->Find(name);if (pos != nullptr){cout << "Sucessly Find : " << endl;return pos;}else{cout << "No Find" << endl;return nullptr;}}T* find(const T& x)//查找{T* pos = cont->Find(x);if (pos != nullptr){cout << "Sucessly Find : " << endl;return pos;}else{cout << "No Find" << endl;return nullptr;}}void change(const string& name)//修改{auto pos = find(name);int size = cont->size();if(pos){cin >> kot(pos);cout << "Sucessly Change" << endl;}else{cout << "Change False, NO Person" << endl;}}void change(const T& x){auto pos = find(x);int size = cont->size();if (pos){/*cin >> kot(pos);*/cin >> kot(pos);cout << "Sucessly Change" << endl;}else{cout << "Change False, NO Person" << endl;}}public:F kot;private:K<T>* cont;
};
以上仅为我自己在学习过程中的一些思路,如果有误或者可以优化的地方欢迎提出,完整的项目地址放在仓库中了,感兴趣可以查看。
study_code: 学习路上写的一些代码