网站结构化数据怎么做简单地网站

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

网站结构化数据,怎么做简单地网站,茶楼 网站,广告设计公司策划书文章目录 1. 前言and框架2. 相对完整的框架3. 模拟实现接口1. 迭代器的引入2. 迭代器的区分list迭代器迭代器的构造list迭代器的实现模拟指针解引用前置和前置–后置和–迭代器#xff01;迭代器- list的const迭代器迭代器模板迭代器是否需要析构#xff0c;拷贝构造和前置–后置和–迭代器迭代器- list的const迭代器迭代器模板迭代器是否需要析构拷贝构造赋值重载3. 迭代器begin()end() 2. push_back3. insert和erase4.push_back,push_front,pop_back,pop_front构造1. 默认构造2. 迭代器区间构造3. 拷贝构造(深拷贝)4. 析构赋值重载 4. list和vector对比 1. 前言and框架 首先我们要明白list的本质是什么list的本质是带头双向循环链表 注意事项 类模板的声明在类前面加一个模板templateclass T即可需要创建一个类list但list的底层是带头双向循环链表。链表则需要结点那我们就还需要创建要给结点的类。【总结就是在列表list中有一个头结点的指针。一个结点里面又会有数据前一个后一个结点指针】这是自己模拟实现的则需要有命名空间否则编译器会调用库里的list如果类里面的内容都是公有的那么则可以使用struct。有公有和私有那么使用class这是惯例但不是规定。在struct中如果没有对某个内容用限定符修饰那么它就是私有。一共两个类。list_node(结点)和list(列表 namespace hou {templateclass Tstruct list_node{//在list_node中存放这个结点的数据前一个和后一个结点指针T _data;list_nodeT* _next;list_nodeT* _prev;};templateclass Tclass list{typedef list_nodeT Node;private:Node* _head;}; }typedef list_nodeT Node;这一步是为了防止大家忘记写模板参数 T。 在class中class没有用限定符修饰是私有的。因此只有在这个类里面才可以用Node在这个类里面Node才代表list_nodeT. 为什么list_node的所有成员全部对外开放 因为链表list在增删查改的时候需要高频地控制list_node的成员变量。如果list_node的成员都私有的话则需要友元函数比较麻烦。 但是全部对外开放不害怕别人修改你的数据吗 其实是很难修改的。我们在使用list的时候是感知不到底层的结点哨兵位之类的东西的。我们只是看到了它的各种接口可以感知到它是双向的。想修改内容需要知道变量的名称命名空间的名字。所以虽然开放但是也安全。 list中大部分用迭代器。在此之前有迭代器失效这个概念这里也会出现。但string中也有迭代器失效但比较少因为大部分用下标访问较少使用迭代器。vector也有迭代器失效。

  1. 相对完整的框架 牢记牢记牢记为了方便我们已经将list_nodeT在list这个类里面typedef为Node。给list构造无参 void empty_init(){_head new Node(); //_head的类型是Node(也就是list_nodeT)结点的指针_head-_prev _head;_head-_next _head;}list(){empty_init();}list_node的构造 list_node(const T x T()):_data(x),_next(nullptr),_prev(nullptr){}迭代器 面向对象的三大特征封装继承多态。 封装可以用类和迭代器来实现。它可以用来“屏蔽底层的实现细节屏蔽个容器的结构差异本质是封装底层细节和差异提供同一的访问方式 只需要提供一个结点即可
    namespace hou {templateclass Tstruct list_node{T _data;list_nodeT
    _next;list_nodeT* _prev;list_node(const T x T()):_data(x),_next(nullptr),_prev(nullptr){}};templateclass Tstruct list_iterator{typedef list_nodeT Node;Node* _node;}; templateclass Tclass list{typedef list_nodeT Node;public:typedef list_iteratorT iterator; void empty_init(){_head new Node(); //_head的类型是Node(也就是list_nodeT)结点的指针_head-_prev _head;_head-_next _head;}list(){empty_init();}private:Node _head;}; }3. 模拟实现接口
  2. 迭代器的引入 把原生指针直接命名为迭代器迭代器的价值在于封装底层不具体暴露底层的实现细节提供统一的访问方式。比如在使用list的时候我们并不能知道底层的结点哨兵位之类的这就是对底层的封装不暴露底层的细节 迭代器有两个特征解引用/– vector和string类它的物理空间是连续的那原生指针就可能是迭代器那解引用得到的就是数据了。但对于空间不连续的list那解引用是得不到数据的node*解引用是node并不是数据。 既然空间已经是不连续了更不用说指向下一个结点了 所以对于list的迭代器原生指针已经不符合我们的需求了我们需要去进行特殊处理进行类的封装。我们可以通过类的封装以及运算符重载支持这样就可以实现像内置类型一样的运算符。
  3. 迭代器的区分 迭代器也分为普通迭代器和const迭代器。 何时用const的迭代器呢当const的容器想用迭代器的时候必须用const的迭代器。【注const修饰之后只有在定义的时候才有初始化的机会可以在定义的时候进行push_back();】 但首先我们要先明白const是在 *左边还是 右边呢 const int p1; //左定值 int* const p2; //右定址const在int*前面则是修饰p1所指向的内容int*这个类型。const在int的后边在p2的前面则const是在修饰p2这个迭代器可能是指针【左定值右定址】 那么在list的const迭代器中const修饰谁呢 在while中有一个步骤是将迭代器这个步骤就决定了const不是用来修饰迭代器/地址/指针因为迭代器还要呢所以const迭代器类似于模拟p1的行为保护指向的对象不被修改迭代器本身可以修改。 迭代器的实现我们需要去考虑普通迭代器和const迭代器。这两种迭代器不同也会带来不同的接口我们先去分开实现(提供一个结点即可) list迭代器 迭代器的构造 这里list的迭代器并不是原生指针而是用一个类封装的。 一个类型的构造函数是用来构造自己类型的对象的迭代器类型要构造迭代器对象就需要构造函数。 templateclass Tstruct list_iterator //struct代表着全开放{typedef list_nodeT Node;Node _node; //迭代器中的一个成员变量公有list_iterator(Node* pnode):_node(pnode){}};list迭代器的实现 templateclass T struct list_iterator {typedef list_nodeT Node;Node* _node;list_iterator(Node* pnode):_node(pnode){} }; 模拟指针 解引用 T operator*(){return _node-_data; //返回此结点的值}前置和前置– 即想要下一个结点的数据但之前的地址是不可取的list的物理空间不连续那我们可以让结点变成结点的_next Self operator(){_node _node-_next;return *this;}Self operator–(){_node _node-_prev;return *this;} 后置和– 就是要给这个变量和–但是此刻用的还是没有经过和–的值 Self operator(int a){Self tmp(*this);//Self是list_iteratorT_node _node-_next;return tmp;}Self operator–(int a){Self tmp(this);//Self是list_iteratorT_node _node-_prev;return tmp;}迭代器 判断迭代器是否相等就需要看这两个迭代器所指向的结点是否相等。 谁是begin()第一个结点。谁是end()哨兵位是。 bool operator!(const Self s){return _node ! s._node;}迭代器- .是直接访问对象的成员类访问成员就用. 【调用类中的成员函数和成员变量用. 】 -是通过对象的指针访问成员。 T operator-(){return _node-_data;}list的const迭代器 我们需要再实现一个单独的类叫做list_const_iterator吧。这个与普通迭代器不同的是类名不同operator和operator-的返回值类型不同 templateclass Tstruct list_const_iterator{typedef list_nodeT Node;typedef list_const_iteratorT Self;Node _node; //迭代器中的一个成员变量公有list_const_iterator(Node* pnode):_node(pnode){}const T operator(){return _node-_data; //返回此结点的值}const T operator-(){return _node-_data;}Self operator(){_node _node-_next;return *this;}Self operator–(){_node _node-_prev;return *this;}Self operator(int a){Self tmp(*this);//Self是list_iteratorT_node _node-_next;return tmp;}Self operator–(int a){Self tmp(this);//Self是list_iteratorT_node _node-_prev;return tmp;}bool operator!(const Self s){return _node ! s._node;}}; 迭代器模板 普通迭代器返回T可读可写const迭代器返回const T,可读不可写上面的代码存在很大的问题代码冗余所以我们应该去解决这个问题我们可以参考源码的实现类模板参数解决这个问题这也是迭代器的强大之处。 只有几个地方不一样代表着普通和const迭代器高度相似。那我们可以用同一个类模板实现它们俩个同一个类模板只要我们传递不同的参数实例化成不同的迭代器 记得将list中的也修改 //typedef list_iteratorT,T,T iterator;//typedef list_iteratorT,const T,const T* iterator; templateclass T,class Ref,class Ptrstruct list_iterator{typedef list_nodeT Node;typedef list_iteratorT, Ref, Ptr Self;Node* _node;Ref operator*(){return _node-_data;}Ptr operator-(){return _node-_data;}Self operator(){_node _node-_next;return *this;}Self operator–(){_node _node-_prev;return *this;}Self operator(int){Self tmp(*this);_node _node-_next;return tmp;}Self operator–(int){Self tmp(*this);_node _node-_prev;return tmp;}bool operator!(const Self s){return _node ! s._node;}bool operator(const Self s){return _node s._node;}};迭代器是否需要析构拷贝构造赋值重载 it访问完该结点不可能将该结点释放掉所以list迭代器不需要析构函数迭代器只是通过这个系欸但访问修改这个容器释放结点是链表的事情不析构——那么也不需要拷贝构造和赋值重载迭代器的拷贝构造和赋值重载我们并不需要自己去手动实现编译器默认生成的就是浅拷贝迭代器只是一个用来访问元素的工具拷贝迭代器也只是复制出了一个一模一样的工具所以是浅拷贝而我们需要的就是浅拷贝这也说明了并不是说如果有指针就需要我们去实现深拷贝。另外迭代器通过结构体指针访问修改链表所以对于迭代器我们并不需要构造函数结点的释放由链表管理。
  4. 迭代器begin()end() 谁是begin()第一个结点。谁是end()哨兵位是。 iterator begin(){return iterator(_head-_next);//调用迭代器构造list_iterator(Node* pnode)}iterator end(){return iterator(_head);//调用迭代器构造list_iterator(Node* pnode)}2. push_back 第一种实现方式 在链表中新插一个结点。 原来的最后一个tail(_head-prev)指向新结点。 新结点的_prev指向之前的最后一个即tail 新结点的_next指向第一个即_head 哨兵位的前一个指向新结点 void push_back(const T x){Node* newnode new Node(x);Node* tail _head-_prev;tail-_next newnode;newnode-_prev tail;newnode-_next _head;_head-_prev newnode;}第二种实现方式 可以先不实现头插/头删先实现insert/erase可以用这个实现头插/头删。
  5. insert和erase 这两个都是写在list中的因为插入或删除链表中的结点。
    insert可以在任意位置插入数据无论是最后一个结点的前面还是哨兵位的前面都可以插入。在哨兵位插入数据哨兵位前一个不就是最后一个数据Return valueAn iterator that points to the first of the newly inserted elements.返回值指向第一个新插入元素的迭代器erase不可以删除哨兵位的数据Return valueAn iterator pointing to the element that followed the last element erased by the function call. This is the container end if the operation erased the last element in the sequence.返回值一个迭代器这个迭代器指向通过函数调用函数所删除的元素的下一个元素fallowed紧跟着也就是下一个如果删除了序列的最后一个元素这就是容器的结尾 重点erase记得最后删除那个结点 4.push_back,push_front,pop_back,pop_front 返回值none push_back是尾插 插入insert是将position这个位置换成插入的结点那不就是position在插入结点之后吗那end()的前面就是最后一个结点。 void push_back(const T x) {insert(end(), x); }头插push_front是在哨兵位之后第一个结点之前。第一个结点也就是lt.begin();) void push_front(const T x) {insert(begin(), x); }尾删返回值none void pop_back() {erase(iterator(end()–); } void pop_front() {erase(begin()); }构造
  6. 默认构造 2. 迭代器区间构造 //迭代器区间构造template class InputIteratorlist(InputIterator first, InputIterator last){empty_init();while (first ! last){push_back(*first);first;}}3. 拷贝构造(深拷贝) 在使用时的样子lt2(lt1); list(const listT lt){empty_init();for (const auto e : lt){push_back(e);}}用范围for进行尾插但是要注意要加上范围for是*it赋值给给e又是一个拷贝e是T类型对象依次取得容器中的数据T如果是string类型不断拷贝push_back之后又销毁。 现代写法 先使用迭代区间构造值也一样再将两个结点的头结点交换头结点不同那指向的下一个结点也就不一样了 void swap(listT lt) {std::swap(_head, lt._head); } list(const listT lt) {empty_init();listT tmp(lt.begin(), lt.end());swap(tmp); }4. 析构 对于list有单独的clear()接口list的析构可以直接复用clear()同时还需要我们去释放掉头结点 同时我们也要知道clear()和~list()有什么区别 clear()只是将来自list容器的所有元素移除再将size修改为0【clear()所有结点删除哨兵位不动】 ~list会将所有结点哨兵位都删除 ~list(){clear();//哨兵位也释放掉delete _head;_head nullptr;}void clear(){iterator it begin();while (it ! end()){it erase(it);//erase()函数会返回(所删除元素)的下一个元素的迭代器,就相当于了}}赋值重载 传统 listT operator(listT lt){if (this ! lt){clear();for (const auto e : lt){push_back(e);}}return *this;}现代 若是想lt2lt3需要先释放lt2可以将参数改为listT lt; 先将lt3拷贝给给ltlt和lt3有一样大的空间一样大的值这个时候可以将lt和lt2交换这样子lt2时lt的内容即和lt3一模一样。但同时lt3也没有被修改 listT operator(listT lt){swap(lt);return *this;}4. list和vector对比 vector:vector的优点在于下标的随机访问尾插尾删效率高CPU高速缓存命中高。而缺点在于前面部分插入删除数据效率低O(N)扩容有消耗还存一定空间浪费。 list:list的优点在于无需扩容按需申请释放在任意位置插入删除O(1)。缺点在于不支持下标的随机访问CPU高速缓存命中低。 vector和list的关系就像是在互补配合 而对于string的insert和erase迭代器也会失效跟vector类似。但是我们并不太关注。因为string的接口参数大部分是下标支持迭代器反而用得少。