C++:用红黑树封装map与set-2

在这里插入图片描述

文章目录

  • 前言
  • 一、红黑树封装map与set中const迭代器
    • 1. 框架的搭建
    • 2. set实现const迭代器
    • 3. map实现const迭代器
  • 二、operator[ ]
    • 1. operator[ ]要达成的样子
    • 2. insert的改变
  • 三. 解决insert里set中的问题
  • 四. 解决map中的operator[ ]
  • 总结用红黑树封装map与set代码


前言

前面我们map与set封装的已经差不多了,接下来还有一些细节需要处理,本片博客主要解决const迭代器以及引发的一些其他问题~😘😘

总后的总结完整的源码封装的源码附上~🥰🥰

想看前面的封装是怎么实现的戳这里哦宝宝~❤️❤️
<( ̄︶ ̄)↗[GO!]


一、红黑树封装map与set中const迭代器

1. 框架的搭建

首先,和以前一样,要写出const迭代器,和以前一样通过控制三个模板参数,从而控制operator*以及operator->的返回值,从而控制普通迭代器与const迭代器。

在这里插入图片描述

改变模板参数,控制返回值:
在这里插入图片描述


2. set实现const迭代器

set的data中储存的是key,而且set是key的模型,我们说key是不可以被改变的,无论你是普通迭代器还是const迭代器,key都不可以被改变。

因此,这里set普通迭代器与set const迭代器都是RBTree::const迭代器,就达到了这样的效果:
在这里插入图片描述

tips:注意这里要加typename,模板类内的东西要在外部使用要告诉编译器哦

因此,我们在封装的时候要这样写:
在这里插入图片描述
这里的iterator是什么呢?是普通的迭代器吗?

答:不是的,这里我们typedef了,这里其实是红黑树中的const_iterator


3. map实现const迭代器

map与set不同,对于set来说,data中存储的是key,因此直接让它的普通迭代器与const迭代器都是const迭代器。

但是!对于map来说就不一样了,map的data中存储的是
pair<key, calue>,对于pair.first是不可以修改的,对于pair.second是需要修改的,如果直接将普通迭代器与const迭代器都是const迭代器,那么pair.first与pair.second都不能修改了。

因此我们需要想出其他的方法!

这里的解决方法是,将map的pair<key, calue>变为pair<const key, calue>,从一开始就限制key是不能够修改的,因此迭代器正常走就可以了~

在这里插入图片描述


二、operator[ ]

1. operator[ ]要达成的样子

对于map来说,因为map是key-value,因此我们要存在operator[ ],

  1. 通过[ ]进行插入
  2. 通过[ ]通过key改变value

因此我们实现operator[ ]是一定要借助insert的。
这里我们前面详细分析过,具体的分析过程戳这里哦

○( ^皿^)っHiahiahia…


2. insert的改变

通过前面的分析我们知道了,为了实现上述的目的,insert的返回值需要变成一个pair

在这里插入图片描述

因此:
在这里插入图片描述
当然,RBTree.h中的insert改了,对应的map与set也要相应的更改:

以下是我们的测试代码:

jyf::map<int, int> m;
m.insert(make_pair(1, 1));
m.insert(make_pair(3, 3));
m.insert(make_pair(2, 2));//bit::map<int, int>::iterator mit = m.begin();
auto mit = m.begin();
while (mit != m.end())
{// 不能修改key,可以修改value//mit->first = 1;mit->second = 2;cout << mit->first << ":" << mit->second << endl;++mit;
}
cout << endl;//func(m);for (const auto& kv : m)
{cout << kv.first << ":" << kv.second << endl;
}
cout << endl;jyf::set<int> s;
s.insert(5);
s.insert(2);
s.insert(2);
s.insert(12);
s.insert(22);
s.insert(332);
s.insert(7);
jyf::set<int>::iterator it = s.begin();
while (it != s.end())
{// 不应该允许修改key
/*	if (*it % 2 == 0){*it += 10;}*/cout << *it << " ";++it;
}
cout << endl;

但是,但是中的但是,就算我们改完了之后还是编译不通过:
在这里插入图片描述

我们看到这里,map好像没问题了,唯一出问题的就是set,因此我们要解决这个问题。


三. 解决insert里set中的问题

那么set为什么会出问题呢?
问题出在set中普通迭代器与const迭代器都是const迭代器!

如下图:
在这里插入图片描述

那这里怎么解决呢?

首先我们来套一层:

pair<typename RBTree<K, K, SetKeyOfT>::iterator, bool> ret来接收Insert的返回值,因此在ret中,pair的iterator是普通的iterator。

然后又用ret.first 与 ret.second来构造pair返回,也就是说pair是不能直接构造的时候这里first用const迭代器来构造,因此需要套一层,最后用普通迭代器构造cosnt迭代器。

pair<iterator, bool> insert(const K& key)
{pair<typename RBTree<K, K, SetKeyOfT>::iterator, bool> ret = _t.Insert(key);return pair<iterator, bool>(ret.first, ret.second);
}

但是只是这样还是编译不通过,还是老问题~

这是因为我们没有提供用普通迭代器构造const迭代器的构造函数:
解决方案如下:

在这里插入图片描述
这样我们的编译就可以通过了:
在这里插入图片描述


四. 解决map中的operator[ ]

V& operator[] (const K& key)
{pair<iterator, bool> ret = insert(make_pair(key, V()));return ret.first->second;
}

以下是测试代码:

jyf::map<string, string> dict;
dict.insert(make_pair("sort", "xxx"));
dict["left"]; // 插入for (const auto& kv : dict)
{cout << kv.first << ":" << kv.second << endl;
}
cout << endl;dict["left"] = "左边"; // 修改
dict["sort"] = "排序"; // 修改
dict["right"] = "右边"; // 插入+修改for (const auto& kv : dict)
{cout << kv.first << ":" << kv.second << endl;
}
cout << endl;

运行结果为:
在这里插入图片描述


总结用红黑树封装map与set代码

RBTree.h

#pragma once#include<iostream>using namespace std;enum Colour
{RED,BLACK
};template<class T>
struct RBTreeNode
{RBTreeNode<T>* _left;RBTreeNode<T>* _right;RBTreeNode<T>* _parent;Colour _col;T _data;RBTreeNode(const T& data): _left(nullptr), _right(nullptr), _parent(nullptr), _col(RED), _data(data){}
};template<class T, class Ptr, class Ref>
struct __TreeIterator
{typedef RBTreeNode<T> Node;typedef __TreeIterator<T, Ptr, Ref> Self;// 解决insert中set的问题typedef __TreeIterator<T, T*, T&> iterator;__TreeIterator(const iterator& it): _node(it._node){};Node* _node;__TreeIterator(Node* node):_node(node){}Ref operator* (){return _node->_data;}Ptr operator-> (){return &(_node->_data);}bool operator!= (const Self& s){return _node != s._node;}bool operator==(const Self& s) const{return _node == s._node;}Self& operator++ (){if (_node->_right != nullptr){Node* curleft = _node->_right;while (curleft->_left){curleft = curleft->_left;}_node = curleft;}else{// 找孩子是父亲左的那个祖先节点,就是下一个要访问的节点Node* cur = _node;Node* parent = _node->_parent;while (parent){if (parent->_left == cur){break;}else{cur = parent;parent = parent->_parent;}}_node = parent;}return *this;}Self& operator--(){if (_node->_left){Node* subRight = _node->_left;while (subRight->_right){subRight = subRight->_right;}_node = subRight;}else{// 孩子是父亲的右的那个节点Node* cur = _node;Node* parent = cur->_parent;while (parent && cur == parent->_left){cur = cur->_parent;parent = parent->_parent;}_node = parent;}return *this;}};template<class K, class T, class KeyOfT>
class RBTree
{typedef RBTreeNode<T> Node;
public:// 同一个类模板,传的不同的参数实例化出的不同类型typedef __TreeIterator<T, T*, T&> iterator;typedef __TreeIterator<T, const T*, const T&> const_iterator;iterator begin(){Node* leftMin = _root;while (leftMin && leftMin->_left){leftMin = leftMin->_left;}return iterator(leftMin);}iterator end(){return iterator(nullptr);}const_iterator begin() const{Node* leftMin = _root;while (leftMin && leftMin->_left){leftMin = leftMin->_left;}return const_iterator(leftMin);}const_iterator end() const{return const_iterator(nullptr);}Node* Find(const K& key){Node* cur = _root;KeyOfT kot;while (cur){if (kot(cur->_data) < key){cur = cur->_right;}else if (kot(cur->_data) > key){cur = cur->_left;}else{return cur;}}return nullptr;}pair<iterator, bool> Insert(const T& data){// 树为空,直接插入然后返回if (_root == nullptr){_root = new Node(data);// 根节点必须是黑色_root->_col = BLACK;return make_pair(iterator(_root), true);}Node* cur = _root;Node* parent = nullptr;KeyOfT kot;while (cur){// 小于往左走if (kot(data) < kot(cur->_data)){parent = cur;cur = cur->_left;}else if (kot(data) > kot(cur->_data))   // 大于往右走{parent = cur;cur = cur->_right;}else{return make_pair(iterator(cur), false);}}cur = new Node(data);// 其他结点初始颜色为红色cur->_col = RED;// 记录cur用于改变颜色后还能找到cur来返回Node* newnode = cur;// 链接if (kot(cur->_data) < kot(parent->_data)){parent->_left = cur;}else{parent->_right = cur;}cur->_parent = parent;// 如果我们parent是黑色,不用处理,就结束了// 情况一:cur为红,parent为红,grandfather为黑,uncle不确定// 用while循环是因为我们要不断的向上调整while (parent && parent->_col == RED){// 首先我们要找到grandfatherNode* grandfather = parent->_parent;// 接下来通过grandfather找到uncle//如果parent是grandfather->_leftif (parent == grandfather->_left){// 说明uncle在右边Node* uncle = grandfather->_right;// uncle存在且为红if (uncle && uncle->_col == RED){// 满足上述情况,开始调整颜色parent->_col = BLACK;uncle->_col = BLACK;grandfather->_col = RED;// 继续向上调整cur = grandfather;parent = cur->_parent;// 但是走到这里有一个问题,如果当前parent就是根,// 并且parent是红色,我们要把它变为黑色,// 处理方式是:出循环后,暴力将_root->_col变为黑色}else    // uncle不存在 或 uncle存在且为黑色{// 判断要怎样旋转// 右单旋if (cur == parent->_left){//     g//   p// cRotateR(grandfather);// 调整颜色parent->_col = BLACK;grandfather->_col = RED;}else // 左右双旋{//     g//   p//		cRotateL(parent);RotateR(grandfather);// 调整颜色cur->_col = BLACK;grandfather->_col = RED;}// 旋转变色完就结束了,这里不加这个也可以,条件判断就会退出break;}}else //(parent == grandfather->_right)  // 如果parent是grandfather->_right{// 说明uncle在左边Node* uncle = grandfather->_left;// uncle存在且为红if (uncle && uncle->_col == RED){// 满足上述情况,开始调整颜色parent->_col = BLACK;uncle->_col = BLACK;grandfather->_col = RED;// 继续向上调整cur = grandfather;parent = cur->_parent;}else    // uncle不存在 或 uncle存在且为黑色{// 判断要怎样旋转// 左单旋if (cur == parent->_right){// g//	  p//       cRotateL(grandfather);// 调整颜色parent->_col = BLACK;grandfather->_col = RED;}else // 右左双旋{// g//	  p// cRotateR(parent);RotateL(grandfather);// 调整颜色cur->_col = BLACK;grandfather->_col = RED;}break;}}}// 这里保证根为黑色_root->_col = BLACK;return make_pair(iterator(newnode), true);}// 左单旋void RotateL(Node* parent){Node* cur = parent->_right;Node* curleft = cur->_left;// 重新链接parent->_right = curleft;if (curleft) // 如果curleft存在{curleft->_parent = parent;}cur->_left = parent;Node* ppnode = parent->_parent;parent->_parent = cur;if (ppnode == nullptr){_root = cur;cur->_parent = nullptr;}else{if (ppnode->_left == parent){ppnode->_left = cur;}else{ppnode->_right = cur;}cur->_parent = ppnode;}}// 右单旋void RotateR(Node* parent){Node* cur = parent->_left;Node* curright = cur->_right;parent->_left = curright;if (curright){curright->_parent = parent;}cur->_right = parent;Node* ppnode = parent->_parent;parent->_parent = cur;if (ppnode == nullptr){cur->_parent = nullptr;_root = cur;}else{if (ppnode->_left == parent){ppnode->_left = cur;}else{ppnode->_right = cur;}cur->_parent = ppnode;}}// 检查是否构建正确bool CheckColour(Node* root, int blacknum, int benchmark){if (root == nullptr){if (blacknum != benchmark)return false;return true;}if (root->_col == BLACK){++blacknum;}if (root->_col == RED && root->_parent && root->_parent->_col == RED){cout << kot(root->data) << "出现连续红色节点" << endl;return false;}return CheckColour(root->_left, blacknum, benchmark)&& CheckColour(root->_right, blacknum, benchmark);}bool IsBalance(){return IsBalance(_root);}bool IsBalance(Node* root){if (root == nullptr)return true;if (root->_col != BLACK){return false;}// 基准值int benchmark = 0;Node* cur = _root;while (cur){if (cur->_col == BLACK)++benchmark;cur = cur->_left;}return CheckColour(root, 0, benchmark);}private:Node* _root = nullptr;
};

myset.h

#pragma once
#include"RBTree.h"namespace jyf
{template<class K>class set{struct SetKeyOfT{const K& operator()(const K& key){return key;}};public:typedef typename RBTree<K, K, SetKeyOfT>::const_iterator iterator;typedef typename RBTree<K, K, SetKeyOfT>::const_iterator const_iterator;iterator begin() const{return _t.begin();}iterator end() const{return _t.end();}pair<iterator, bool> insert(const K& key){pair<typename RBTree<K, K, SetKeyOfT>::iterator, bool> ret = _t.Insert(key);return pair<iterator, bool>(ret.first, ret.second);}private:RBTree<K, K, SetKeyOfT> _t;};
}

mymap.h

#pragma once#include"RBTree.h"namespace jyf
{template<class K, class V>class map{struct MapKeyOfT{const K& operator()(const pair<const K, V>& kv){return kv.first;}};public:typedef typename RBTree<K, pair<const K, V>, MapKeyOfT>::iterator iterator;typedef typename RBTree<K, pair<const K, V>, MapKeyOfT>::const_iterator const_iterator;iterator begin(){return _t.begin();}iterator end(){return _t.end();}const_iterator begin() const{return _t.begin();}const_iterator end() const{return _t.end();}V& operator[] (const K& key){pair<iterator, bool> ret = insert(make_pair(key, V()));return ret.first->second;}pair<iterator, bool> insert(const pair<K, V>& kv){return _t.Insert(kv);}private:RBTree<K, pair<const K, V>, MapKeyOfT> _t;};
}

test.cpp

#include<iostream>
#include<string>
using namespace std;#include"mymap.h"
#include"myset.h"//void func(const jyf::map<int, int>& m)
//{
//	//auto mit = m.begin();
//	jyf::map<int, int>::const_iterator mit = m.begin();
//	while (mit != m.end())
//	{
//		// 不能修改key,不能修改value
//		//mit->first = 1;
//		//mit->second = 2;
//
//		cout << mit->first << ":" << mit->second << endl;
//		++mit;
//	}
//	cout << endl;
//}int main()
{jyf::map<int, int> m;m.insert(make_pair(1, 1));m.insert(make_pair(3, 3));m.insert(make_pair(2, 2));//bit::map<int, int>::iterator mit = m.begin();auto mit = m.begin();while (mit != m.end()){// 不能修改key,可以修改value//mit->first = 1;mit->second = 2;cout << mit->first << ":" << mit->second << endl;++mit;}cout << endl;//func(m);for (const auto& kv : m){cout << kv.first << ":" << kv.second << endl;}cout << endl;jyf::set<int> s;s.insert(5);s.insert(2);s.insert(2);s.insert(12);s.insert(22);s.insert(332);s.insert(7);jyf::set<int>::iterator it = s.begin();while (it != s.end()){// 不应该允许修改key/*	if (*it % 2 == 0){*it += 10;}*/cout << *it << " ";++it;}cout << endl;//for (const auto& e : s)//{//	cout << e << " ";//}//cout << endl;jyf::map<string, string> dict;dict.insert(make_pair("sort", "xxx"));dict["left"]; // 插入for (const auto& kv : dict){cout << kv.first << ":" << kv.second << endl;}cout << endl;dict["left"] = "左边"; // 修改dict["sort"] = "排序"; // 修改dict["right"] = "右边"; // 插入+修改for (const auto& kv : dict){cout << kv.first << ":" << kv.second << endl;}cout << endl;return 0;
}

🥰🥰🥰到这里就结束啦,创作不易,如果感觉对您有帮助的话,求求一个一键三连,谢谢各位大佬~🥰🥰🥰

在这里插入图片描述

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.rhkb.cn/news/478411.html

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

软件/游戏提示:mfc42u.dll没有被指定在windows上运行如何解决?多种有效解决方法汇总分享

遇到“mfc42u.dll 没有被指定在 Windows 上运行”的错误提示&#xff0c;通常是因为系统缺少必要的运行库文件或文件损坏。以下是多种有效的解决方法&#xff0c;可以帮助你解决这个问题&#xff1a; 原因分析 出现这个错误的原因是Windows无法找到或加载MFC42u.dll文件。这可…

07 初始 Oracle 优化器

查询优化器&#xff0c;简称优化器&#xff0c;是数据库最核心的组件之一。我们在这个系列的第一篇文章中已经给大家介绍了&#xff0c;优化器会参与到SQL语句的解析过程中&#xff0c;用来生成SQL语句的执行计划&#xff0c;直接决定SQL语句执行性能的优劣。 什么是执行计划 …

累积局部效应 (ALE) 图分析记录

Git地址&#xff1a;https://github.com/blent-ai/ALEPython/tree/dev 查看源码需要pip install alepython安装&#xff0c;这边查看源码发现就实际就一个py文件而已&#xff0c;我懒得再去安装&#xff0c;故直接下载源码&#xff0c;调用方法也可&#xff1b; # -*- coding:…

远程控制软件:探究云计算和人工智能的融合

在数字化时代&#xff0c;远程控制工具已成为我们工作与生活的重要部分。用户能够通过网络远程操作和管理另一台计算机&#xff0c;极大地提升了工作效率和便捷性。随着人工智能&#xff08;AI&#xff09;和云计算技术的飞速发展&#xff0c;远程控制工具也迎来了新的发展机遇…

正则表达式灾难:重新认识“KISS原则”的意义

RSS Feed 文章标题整理 微积分在生活中的应用与思维启发 捕鹿到瞬时速度的趣味探索 微积分是一扇通往更广阔世界的门&#xff0c;从生活中学习思维的工具。 数据库才是最强架构 你还在被“复杂架构”误导吗&#xff1f; 把业务逻辑写入数据库&#xff0c;重新定义简单与效率。…

网络原理(一):应用层自定义协议的信息组织格式 初始 HTTP

目录 1. 应用层 2. 自定义协议 2.1 根据需求 > 明确传输信息 2.2 约定好信息组织的格式 2.2.1 行文本 2.2.2 xml 2.2.3 json 2.2.4 protobuf 3. HTTP 协议 3.1 特点 4. 抓包工具 1. 应用层 在前面的博客中, 我们了解了 TCP/IP 五层协议模型: 应用层传输层网络层…

【JUC-Interrupt】中断相关概念

线程中断 一、相关概念二、API2.1、isInterrupted方法2.2、interrupted方法2.3、interrupt 三、总结&#xff1a; 一、相关概念 一个线程不应该由其他线程中断或停止&#xff0c;应该有线程自己来决定。 在Java中没有办法立即停止一个线程&#xff0c;因此提供了用于停止线程…

直播技术-Android基础框架

目录 &#xff08;一&#xff09;直播间架构 &#xff08;二&#xff09;核心任务调度机制 &#xff08;1&#xff09;复制从滑动直播间加载流程 &#xff08;2&#xff09;核心任务调度机制-代码设计 &#xff08;3&#xff09;核心任务调度机制-接入指南 (三&#xff0…

【es6】原生js在页面上画矩形添加选中状态高亮及显示调整大小控制框(三)

接上篇文章&#xff0c;这篇实现下选中当前元素显示调整大小的控制框&#xff0c;点击document取消元素的选中高亮状态效果。 实现效果 代码逻辑 动态生成控制按钮矩形,并设置响应的css // 动态添加一个调整位置的按钮addScaleBtn(target) {const w target.offsetWidth;con…

ArcGIS应用指南:ArcGIS制作局部放大地图

在地理信息系统&#xff08;GIS&#xff09;中&#xff0c;制作详细且美观的地图是一项重要的技能。地图制作不仅仅是简单地将地理数据可视化&#xff0c;还需要考虑地图的可读性和美观性。局部放大图是一种常见的地图设计技巧&#xff0c;用于展示特定区域的详细信息&#xff…

记录一些PostgreSQL操作

本文分享一些pg操作 查看版本 select version(); PostgreSQL 11.11 查看安装的插件 select * from pg_available_extensions; 查看分词效果 select ‘我爱北京天安门,天安门上太阳升’::tsvector; ‘天安门上太阳升’:2 ‘我爱北京天安门’:1select to_tsvector(‘我爱北京天…

RHCSA作业2

压缩 将整个 /etc 目录下的文件全部打包并用 gzip 压缩成/back/etcback.tar.gz [rootjyh ~]# cd /etc [rootjyh etc]# tar -czf etcback.tar.gz /etc tar: Removing leading / from member names tar: /etc/etcback.tar.gz: file changed as we read it [rootjyh etc]# ls使当…

大语言模型(LLM)安全:十大风险、影响和防御措施

一、什么是大语言模型&#xff08;LLM&#xff09;安全&#xff1f; 大语言模型&#xff08;LLM&#xff09;安全侧重于保护大型语言模型免受各种威胁&#xff0c;这些威胁可能会损害其功能、完整性和所处理的数据。这涉及实施措施来保护模型本身、它使用的数据以及支持它的基…

递推进阶与入门递归

一、递推进阶&#xff0c;勇攀高峰 昆虫繁殖 题目描述 科学家在热带森林中发现了一种特殊的昆虫&#xff0c;这种昆虫的繁殖能力很强。每对成虫过X个月产Y对卵&#xff0c;每对卵要过两个月长成成虫。假设每个成虫不死&#xff0c;第一个月只有一对成虫&#xff0c;且卵长成成虫…

深入浅出:JVM 的架构与运行机制

一、什么是JVM 1、什么是JDK、JRE、JVM JDK是 Java语言的软件开发工具包&#xff0c;也是整个java开发的核心&#xff0c;它包含了JRE和开发工具包JRE&#xff0c;Java运行环境&#xff0c;包含了JVM和Java的核心类库&#xff08;Java API&#xff09;JVM&#xff0c;Java虚拟…

极客大挑战2024wp

极客大挑战2024wp web 和misc 都没咋做出来&#xff0c;全靠pwn✌带飞 排名 密码学和re没做出几个&#xff0c;就不发了 web ez_pop 源代码 <?php Class SYC{public $starven;public function __call($name, $arguments){if(preg_match(/%|iconv|UCS|UTF|rot|quoted…

C++设计模式-策略模式-StrategyMethod

动机&#xff08;Motivation&#xff09; 在软件构建过程中&#xff0c;某些对象使用的算法可能多种多样&#xff0c;经常改变&#xff0c;如果将这些算法都编码到对象中&#xff0c;将会使对象变得异常复杂&#xff1b;而且有时候支持不使用的算法也是一个性能负担。 如何在运…

【初阶数据结构和算法】leetcode刷题之设计循环队列

文章目录 一、实现循环队列1.大致思路分析2.循环队列的结构定义和初始化结构定义初始化 3.循环队列的判空和判满判空和判满难点分析判空判满 4.循环队列的入队列和出队列入队列出队列 5.循环队列取队头和队尾元素取队头元素取队尾元素 6.循环队列的销毁7.最后题解源码 一、实现…

【网络通信】数据集合集!

本文将为您介绍经典、热门的数据集&#xff0c;希望对您在选择适合的数据集时有所帮助。 1 RITA 更新时间&#xff1a;2024-11-22 访问地址: GitHub 描述&#xff1a; RITA 是一个用于网络流量分析的开源框架。 该框架以 TSV 或 JSON 格式提取 Zeek 日志&#xff0c;目前支…

.net core MVC入门(一)

文章目录 项目地址一、环境配置1.1 安装EF core需要包1.2 配置数据库连接二、使用EF创建表2.1 整体流程梳理2.1 建表详细流程三、添加第一个视图3.1整体流程梳理3.1 添加视图,并显示在web里四、使用EF增加Catogory数据,并且读取数据到页面4.1整体流程梳理4.2 实现五、增加Cat…