设计模式22-迭代器模式
设计模式22-迭代器模式
- 迭代器模式(Iterator Pattern)
- 动机
- 定义结构
- 定义
- 结构
- 结构图解释
- 注意事项
- C++代码推导
- 多态属性(虚函数)实现迭代器
- 1. **返回值问题**
- 2. **对象切割问题**
- 3. **内存管理问题**
- 4. **迭代器生命周期问题**
- 5. **接口设计问题**
- 6. **接口返回值的问题**
- 模版形式实现迭代器
- 为什么以模版形式实现迭代器而不使用多态性来实现迭代器
- 1. **性能考虑**
- 2. **灵活性和类型安全**
- 3. **类型擦除的避免**
- 4. **代码复用和泛型编程**
- 5. **减少内存开销**
- 6. **模板元编程**
- 什么时候选择多态性?
- 总结
- 优缺点
- 应用场景
- 总结
迭代器模式(Iterator Pattern)
动机
- 软件构建过程集合对象内部结构经常变化各异。但对于这些集合对象,我们希望在不暴露其内部结构的同时,也可以让外部客户代码透明的访问其中包含的元素,同时这种透明便利也为同一种算法在多种集合对象上进行操作提供了可能。
- 使用面向对象技术将这种便利机制抽象为迭代器对象。为应对变化中的集合对象提供了一种优雅的方式。
- 在软件开发中,经常需要遍历某种集合对象(如数组、链表、树等)中的元素。集合的具体实现通常不应暴露给客户端,以确保集合的封装性和灵活性。为了遍历集合中的元素而不暴露集合的内部结构,迭代器模式引入了一种通用的遍历机制,使得客户端可以遍历集合而不关心集合的内部实现。
定义结构
定义
迭代器模式提供一种方法,顺序访问一个聚合对象中的各个元素,而不暴露该对象的内部表示。
结构
结构图解释
1. 类和接口
-
Aggregate(聚合类):这是一个集合类,它提供了创建迭代器对象的方法(通常是
CreateIterator()
或类似的命名)。在这个例子中,Aggregate
类有一个Createlterator()
方法,该方法返回一个新的迭代器实例。这个迭代器实例是基于当前Aggregate
对象创建的,以便能够遍历它的元素。注意,图片中的Createlterator()
方法名似乎有一个小错误,应该是CreateIterator()
,但我将遵循图片中的实际文本。 -
lterator(迭代器接口):这是一个定义迭代器行为的接口。它声明了用于遍历元素的方法,如
First()
(定位到序列的第一个元素),Next()
(移动到序列中的下一个元素),IsDone()
(检查序列中是否还有更多的元素),以及CurrentItem()
(获取当前位置的元素)。在标准的命名习惯中,接口名通常是单数且以大写字母开头,但这里使用了lterator
,可能是一个打字错误,正确的可能是Iterator
。 -
Client(客户端类):这是使用迭代器遍历聚合类中的元素的类。它持有一个迭代器对象的引用,并通过这个迭代器来遍历聚合类中的元素。在这个例子中,
Client
类有一个名为lterator
的私有成员变量(可能是Iterator
的一个实例,但这里使用了lterator
),并使用了迭代器的First()
、Next()
和IsDone()
方法来遍历元素。
2. 实现类
-
ConcreteAggregate(具体聚合类):这是
Aggregate
类的一个具体实现,它实现了CreateIterator()
方法来返回一个Concretelterator
(应为ConcreteIterator
)实例。这个实例能够遍历ConcreteAggregate
对象中的元素。 -
Concretelterator(具体迭代器类):这是
lterator
(应为Iterator
)接口的一个具体实现。它实现了接口中定义的所有方法,包括First()
、Next()
、IsDone()
和CurrentItem()
,以便能够遍历ConcreteAggregate
对象中的元素。在这个例子中,Concreteelterator
的构造函数接收一个ConcreteAggregate
对象作为参数,以便它能够知道要遍历哪个集合。注意,这里的Concretelterator
也是一个小错误,正确的应该是ConcreteIterator
。
注意事项
- 图片中的类名和方法名中存在一些拼写错误,如
lterator
应该是Iterator
,Createlterator()
应该是CreateIterator()
,Currentitem()
应该是CurrentItem()
。 - 迭代器模式的核心在于将遍历聚合类对象的责任从聚合类本身转移到迭代器对象上,这有助于保持聚合类的简洁,并使得遍历逻辑可以在不同的迭代器之间重用。
希望这个解释能够帮助您理解这张关于迭代器模式的结构图。
C++代码推导
多态属性(虚函数)实现迭代器
template<typename T>
class Iterator
{
public:virtual void first() = 0;virtual void next() = 0;virtual bool isDone() const = 0;virtual T& current() = 0;
};template<typename T>
class MyCollection{public:Iterator<T> GetIterator(){//...}};template<typename T>
class CollectionIterator : public Iterator<T>{MyCollection<T> mc;
public:CollectionIterator(const MyCollection<T> & c): mc(c){ }void first() override {}void next() override {}bool isDone() const override{}T& current() override{}
};void MyAlgorithm()
{MyCollection<int> mc;Iterator<int> iter= mc.GetIterator();for (iter.first(); !iter.isDone(); iter.next()){cout << iter.current() << endl;}}
这种迭代器实现方式存在一些问题,使得它在实际应用中可能并不是最佳选择。以下是一些关键问题及原因:
1. 返回值问题
Iterator<int> iter = mc.GetIterator();
-
问题:
Iterator<int>
是一个抽象类,不能直接实例化。当你尝试返回一个Iterator<int>
对象时,编译器会报错,因为不能创建一个抽象类的实例。 -
改进:应返回一个指向
Iterator<int>
的指针或者使用智能指针,例如std::unique_ptr<Iterator<int>>
,以确保可以动态地分配一个具体的迭代器实例。std::unique_ptr<Iterator<T>> GetIterator() {return std::make_unique<CollectionIterator<T>>(*this); }
2. 对象切割问题
Iterator<int> iter = mc.GetIterator();
-
问题:即使
Iterator<int>
能够返回一个对象(假设Iterator<int>
不是抽象类),由于Iterator<int>
的拷贝会发生对象切割问题,导致派生类的部分(CollectionIterator<int>
的特定实现)丢失,剩下的只是一个基类的对象,这会使得运行时的多态行为失效。 -
改进:避免对象切割,返回迭代器的指针或引用。
3. 内存管理问题
-
问题:如果使用原始指针(如
Iterator<T>*
)返回具体迭代器,会面临内存管理问题。如果用户忘记释放内存,可能会导致内存泄漏。 -
改进:使用智能指针来管理内存,例如
std::unique_ptr
或std::shared_ptr
。智能指针会自动管理对象的生命周期,避免内存泄漏。
4. 迭代器生命周期问题
-
问题:
CollectionIterator
持有MyCollection
的副本(通过成员变量mc
)。这意味着迭代器中保存的集合与原集合可能是两个不同的对象。如果集合发生改变,迭代器将无法反映这些变化。 -
改进:迭代器应持有集合的引用而不是副本,以确保它们始终引用相同的集合对象。
template<typename T> class CollectionIterator : public Iterator<T> {const MyCollection<T>& mc; public:CollectionIterator(const MyCollection<T>& c) : mc(c) { }// 实现迭代器方法 };
5. 接口设计问题
-
问题:迭代器接口设计可能存在一些细节问题。例如,
current()
方法返回对当前元素的引用时,如果集合为空,调用current()
会导致未定义行为。 -
改进:在实现
current()
时,应该确保集合非空或抛出异常以避免未定义行为。也可以提供一个安全的接口,如返回指向元素的指针。
6. 接口返回值的问题
-
问题:
GetIterator()
方法在你的设计中返回Iterator<T>
,但是实际应该返回指向具体Iterator
的指针或引用,以支持多态。 -
改进:改用返回智能指针或指针以支持多态:
std::unique_ptr<Iterator<T>> GetIterator() {return std::make_unique<CollectionIterator<T>>(*this); }
模版形式实现迭代器
以下是一个使用迭代器模式遍历一个简单集合(例如整数数组)的C++代码示例。
迭代器接口类:
template <typename T>
class Iterator {
public:virtual ~Iterator() {}virtual T first() = 0;virtual T next() = 0;virtual bool isDone() const = 0;virtual T currentItem() const = 0;
};
具体迭代器类:
template <typename T>
class ConcreteIterator : public Iterator<T> {
private:const std::vector<T>& collection;size_t currentIndex;public:ConcreteIterator(const std::vector<T>& collection): collection(collection), currentIndex(0) {}T first() override {return collection[0];}T next() override {currentIndex++;if (currentIndex < collection.size()) {return collection[currentIndex];}throw std::out_of_range("Iterator out of range");}bool isDone() const override {return currentIndex >= collection.size();}T currentItem() const override {if (currentIndex < collection.size()) {return collection[currentIndex];}throw std::out_of_range("Iterator out of range");}
};
聚合接口类:
template <typename T>
class Aggregate {
public:virtual ~Aggregate() {}virtual Iterator<T>* createIterator() const = 0;
};
具体聚合类:
template <typename T>
class ConcreteAggregate : public Aggregate<T> {
private:std::vector<T> items;public:ConcreteAggregate(std::initializer_list<T> elements) : items(elements) {}Iterator<T>* createIterator() const override {return new ConcreteIterator<T>(items);}size_t size() const {return items.size();}T getItem(size_t index) const {if (index < items.size()) {return items[index];}throw std::out_of_range("Index out of range");}
};
客户端代码:
#include <iostream>
#include <vector>
#include <stdexcept>int main() {ConcreteAggregate<int> aggregate({1, 2, 3, 4, 5});Iterator<int>* iterator = aggregate.createIterator();for (iterator->first(); !iterator->isDone(); iterator->next()) {std::cout << iterator->currentItem() << " ";}delete iterator;return 0;
}
运行结果:
1 2 3 4 5
为什么以模版形式实现迭代器而不使用多态性来实现迭代器
在 C++ 中,实现迭代器时选择使用模板而非多态性(即虚函数)主要有以下几个原因:
1. 性能考虑
模板是在编译时进行类型替换的,这意味着模板代码在编译时生成特定类型的代码实例,而不需要在运行时进行类型检查或虚函数调用。这带来了以下几个性能上的好处:
-
无虚函数开销:使用模板可以避免虚函数调用的开销。虚函数调用通常涉及通过虚函数表(vtable)间接访问函数,可能导致一些额外的性能开销。而模板直接生成目标类型的代码,函数调用可以内联,从而提高运行时性能。
-
优化机会:编译器在模板实例化时有更多的优化机会。由于编译器知道确切的类型,可以进行更多的优化,比如内联、消除不必要的操作等。
2. 灵活性和类型安全
-
强类型检查:模板提供了更强的类型安全性,因为所有类型信息在编译时就已经确定。模板迭代器在编译时会检查是否所有操作都符合类型约束,避免了在运行时发生类型错误。
-
支持更多容器:模板允许你为不同类型的集合(如
std::vector<T>
、std::list<T>
等)生成不同的迭代器实现,而不需要定义多个派生类。这样,模板迭代器可以更好地适应各种容器类型,而不需要为每种类型手动定义派生类。
3. 类型擦除的避免
- 避免类型擦除:在多态性实现中,为了支持不同类型的对象,通常需要使用指针或引用来指向基类。这涉及到类型擦除(type erasure),即在运行时丢失部分类型信息。而模板不会丢失类型信息,能够保留完整的类型信息,并在编译时确保类型的正确性。
4. 代码复用和泛型编程
-
代码复用:模板允许编写泛型代码,这样可以避免重复为不同类型编写类似的代码。通过使用模板,可以编写一次泛型迭代器,然后在不同类型的集合上复用该迭代器代码。
-
泛型编程的自然适应性:模板是一种强大的工具,尤其适用于泛型编程。在 C++ 标准库中,几乎所有容器的迭代器都是通过模板实现的,形成了一个一致的泛型编程体系(如 STL)。这使得模板迭代器可以无缝地与 C++ 标准库的算法(如
std::sort
,std::find
等)协同工作。
5. 减少内存开销
- 避免额外的对象分配:使用多态性时,通常需要动态分配内存以存储不同类型的对象,这会增加内存开销。模板迭代器由于不需要动态分配内存,可以在栈上分配,并且避免了额外的对象开销。
6. 模板元编程
- 支持高级编程技巧:模板不仅仅是类型安全和高效的,它还支持复杂的模板元编程技巧。这种能力允许在编译时执行计算和生成代码,从而进一步优化代码的执行效率。
什么时候选择多态性?
尽管模板具有很多优势,但在某些情况下,多态性(即使用虚函数)仍然是合适的选择:
-
需要在运行时处理异构集合:如果需要在运行时处理不同类型的对象(如一个集合中包含不同类型的对象),模板可能无法提供直接的支持,此时使用多态性可以更好地处理这些需求。
-
接口稳定性:如果需要提供一个稳定的接口供外部使用,而不希望外部依赖模板实现(模板通常定义在头文件中,容易暴露实现细节),则可以选择使用虚函数接口来隐藏实现。
总结
在 C++ 中,模板实现迭代器的方式由于性能、类型安全性、灵活性、和代码复用性等方面的优势,通常是优于使用多态性的。模板通过在编译时确定类型和生成代码,避免了运行时开销,使得迭代器更高效且灵活。但在处理需要运行时多态性或异构集合的情况下,多态性仍然是不可替代的。
优缺点
优点:
- 一致的遍历接口:迭代器模式为不同的聚合结构提供了一致的遍历接口,使得遍历操作与聚合对象的实现解耦。
- 封装性:迭代器模式可以避免暴露聚合对象的内部表示,保持了集合对象的封装性。
- 灵活性:可以为同一个聚合对象创建不同的迭代器,以支持不同的遍历方式(如正向、反向遍历)。
缺点:
- 可能增加复杂性:引入了额外的迭代器类,可能会增加系统的复杂性,特别是当需要支持多种迭代方式时。
- 对象数量增加:对于大型聚合对象,迭代器的创建和管理可能会导致较多的对象实例,增加内存开销。
应用场景
迭代器模式适用于以下场景:
- 需要遍历集合对象:如数组、链表、树形结构等,需要对集合对象中的元素进行遍历时。
- 需要多种遍历方式:如正序、倒序或特定条件下的遍历,可以为同一个聚合对象定义不同的迭代器。
- 隐藏聚合对象的实现细节:希望客户端在遍历集合时无需了解集合的具体实现(如内部数据结构)时。
总结
- 有些模式运用的技术机制可能会过时但是它的思想不会过时
- 迭代抽象:访问一个聚合对象的内容,而无需暴露它的内部表示。
- 迭代多态:在遍历不同的集合结构提供一个统一的接口。从而支持同样的算法在不同的集合结构上进行操作。
- 迭代器的健壮性考虑:便利的同时更改迭代器所在的集合结构会导致问题。
- 迭代器模式为集合对象提供了一种通用的遍历接口,使得客户端可以以一致的方式访问集合中的元素,而不需要了解集合的内部结构。通过封装遍历逻辑,迭代器模式有效地解耦了集合的实现和使用,但也可能引入额外的复杂性和开销。在需要对复杂集合进行多种遍历时,迭代器模式特别有用。
相关文章:

设计模式22-迭代器模式
设计模式22-迭代器模式 迭代器模式(Iterator Pattern)动机定义结构定义结构结构图解释注意事项 C代码推导多态属性(虚函数)实现迭代器1. **返回值问题**2. **对象切割问题**3. **内存管理问题**4. **迭代器生命周期问题**5. **接口…...

编程深水区之并发⑥:C#的线程池
绝大多数情况下,我们都应该使用CLR线程池,而不是直接操作Thread,本章节介绍直接操作线程池的ThreadPool,但实际开发中也很少直接使用它。 一、CLR和线程池 1.1 CLR的主要工作 CLR(Common Language Runtime࿰…...

KCTF 闯关游戏:1 ~ 7 关
前言 看雪CTF平台是一个专注于网络安全技术竞赛的在线平台,它提供了一个供网络安全爱好者和技术专家进行技术交流、学习和竞技的环境。CTF(Capture The Flag,夺旗赛)是网络安全领域内的一种流行竞赛形式,起源于1996年…...

【海贼王航海日志:前端技术探索】一篇文章带你走进JavaScript(二)
目录 1 -> 基础数据类型 1.1 -> 条件语句 1.1.1 if语句 1.2 -> 分支语句 1.2.1 -> switch语句 1.3 -> 循环语句 1.3.1 -> while循环 1.3.2 -> continue 1.3.3 -> break 1.3.4 -> for循环 1.4 -> 数组 1.4.1 -> 创建数组 1.4.2 -…...

鸿蒙内核源码分析(进程管理篇) | 谁在管理内核资源?
官方基本概念 从系统的角度看,进程是资源管理单元。进程可以使用或等待CPU、使用内存空间等系统资源,并独立于其它进程运行。 OpenHarmony内核的进程模块可以给用户提供多个进程,实现了进程之间的切换和通信,帮助用户管理业务程序…...

SQLALchemy 自动从数据库中映射
SQLALchemy 自动从数据库中映射 使用`automap_base`注意事项在SQLAlchemy中,自动从数据库中映射表到Python类(也称为“反射”或“逆向工程”)是一个常见的需求,尤其是在你已经有了一个现有的数据库,并希望快速地为它创建一个ORM模型时。SQLAlchemy提供了工具来帮助你完成这…...

C++ stack与queue的使用与简单实现
目录 0. 适配器 1. stack的简要介绍 2. stack的简单使用 3. queue的简要介绍 4. queue的简单使用 STL标准库中stack和queue的底层结构 deque简单介绍 5. stack的模拟实现 6. queue的模拟实现 0. 适配器 在文章开始前我们先了解一下适配器的概念 适配器是一种设计模式(设计…...

【CS.DB】数据库-关系型数据库-MySQL-3.3.创建和管理表
1000.04.CS.DB-Database-Relational-MySQL-3.3.创建和管理表-Created: 2023-03-08.Thursday17:39 1. 创建和管理表 在 MySQL 中,创建和管理表是数据库操作的基础。以下是创建和管理表的主要步骤和方法。 1.1 定义表结构 定义表结构包括指定表的名称、列的名称和数…...

Ceph分布式存储系统的搭建与使用
目录 一. 环境准备 二. 安装Docker 三. admin节点安装cephadm 四. admin节点给另外四个主机导入镜像 五. 向集群中添加节点 六. Ceph使用 列出可用设备 清除设备数据---针对有数据的设备 检查 OSD 状态 Ceph 集群中添加一个新的 OSD 查看集群的健康状态 指定MDS 列…...

通过Redsocks将Kali Linux的流量进行代理
Redsocks 是一个代理重定向工具,可以将流量通过 SOCKS 或 HTTP 代理传递。你可以使用它在 Kali Linux 中将流量通过代理服务器。以下是设置和使用 Redsocks 的步骤: 1. 安装 Redsocks Redsocks 通常在 Kali Linux 上不可用,需要手动安装。首…...

基于java五台山景点购票系统(源码+论文+部署讲解等)
博主介绍: ✌我是阿龙,一名专注于Java技术领域的程序员,全网拥有10W粉丝。作为CSDN特邀作者、博客专家、新星计划导师,我在计算机毕业设计开发方面积累了丰富的经验。同时,我也是掘金、华为云、阿里云、InfoQ等平台的优…...

基于springboot的网上服装商城
TOC springboot182基于springboot的网上服装商城 第一章 课题背景及研究内容 1.1 课题背景 信息数据从传统到当代,是一直在变革当中,突如其来的互联网让传统的信息管理看到了革命性的曙光,因为传统信息管理从时效性,还是安全性…...

QT、C++简单界面设计
#include "mywidget.h"MyWidget::MyWidget(QWidget *parent): QWidget(parent) {---------------------窗口设置----------------------this->setWindowTitle("南城贤子摄影工作室");//设置窗口标题this->setWindowIcon(QIcon("d:\\Pictures\\C…...

代码随想录算法训练营43期 | Day 10——栈与队列part1
代码随想录算法训练营 代码随想录算法训练营43期 | Day 10232.用栈实现队列225. 用队列实现栈20. 有效的括号1047.删除字符串中的所有相邻重复项 代码随想录算法训练营43期 | Day 10 232.用栈实现队列 class MyQueue { public:stack<int> sIn;stack<int> sOut;My…...

Java中常用的设计模式
一、什么是设计模式 设计模式(Design pattern)是一套被反复使用、多数人知晓的、经过分类编目的、代码设计经验的总结。使用设计模式是为了可重用代码、让代码更容易被他人理解、保证代码可靠性。 毫无疑问,设计模式于己于他人于系统都是多赢的,设计模式使代码编制真正工程…...

leetcode 11-20(2024.08.15)
立个flag,1-100题每天分配10题,不会就先空着(7)。 1. 11:盛最多水的容器 class Solution:def maxArea(self, height: List[int]) -> int:res 0left 0right len(height) - 1while left < right:area (right…...

C语言整数溢出的问题
目录 补漏: 问题展现 解析 C的解决方案 补漏: 昨天我在开头提到-1的二进制如何表示,我在这里简单分析一下。 首先我们要明白有符号的数转换是需要补码的,所以我们想这个问题之前将补码的规则思考一遍(首先将有符号…...

Linux学习之路 -- 进程 -- 进程间通信 -- 管道通信
本文主要介绍进程通信中的管道通信。 前面我们学习进程的过程中,我们知道,进程是具有独立性的。这也就导致了进程不能够直接地把数据进行传递。为了实现进程之间地通信,我们就需要通过另外地方式来实现进程之间数据地传递。 1.进程通信的目…...

GB/T 38082-2019 生物降解塑料购物袋检测
生物降解塑料购物袋是指以生物降解树脂为主要原料制得的,具有提携结构的,在销售、服务等场所用于盛装及携提商品的袋制品。 GB/T 38082-2019 生物降解塑料购物袋检测项目: 检测项目 测试标准 尺寸偏差 GB/T 38082 感官 GB/T 38082 提掉…...

docker数据卷和资源控制
目录 数据卷 实现数据卷 宿主机和容器之间进行数据共享 容器与容器之间进行数据共享 容器互联 docker容器的资源控制 cpu 1.设置cpu资源控制(比重) 2. 设置cpu的资源占用比(权重) 3.设置容器绑定cpu 内存 1.内存限制 …...

Kafka系统及其角色
Apache Kafka系统介绍 Apache Kafka 是由 LinkedIn 公司最初开发的一个高性能、分布式的消息传递系统。它被设计为一个可扩展、持久、分布式的流式处理平台,以满足 LinkedIn 在实时数据处理方面的需求 。Kafka 的诞生源于 LinkedIn 需要处理海量数据时现有消息队列系…...

从零开始构建霸王餐返利APP的技术路线与挑战
从零开始构建霸王餐返利APP的技术路线与挑战 大家好,我是阿可,微赚淘客系统及省赚客APP创始人,是个冬天不穿秋裤,天冷也要风度的程序猿! 在电商领域,霸王餐返利APP作为一种新兴的商业模式,为用…...

安装Jmeter,配置jdk
注意点: java的jdk和jmeter的版本相匹配 ! ! ! 目前我使用的是1.8的的,jmeter使用的是5.6.3 JDK下载地址:https://www.oracle.com/cn/java/technologies/downloads 别管,直接傻瓜式安装点点就完了... 1.电脑-属性-高级系统设置-环境变量 2.系统变量-新建-变量…...

Aria2@RPC下载@Alist批量下载
文章目录 abstractAria2 RPC 概述RPC 的主要功能在线文档aria2的配置文件与启动选项使用配置文件设置aria2 rpc功能Aria2关于rpc的离线文档 Aria2 RPC 重要和常用选项1. enable-rpc2. rpc-listen-port3. rpc-secret4. rpc-listen-all5. rpc-allow-origin-all6. rpc-max-request…...

神经串联式语音转换:对基于串联的单次语音转换方法的再思考 论文笔记
NEURAL CONCATENATIVE SINGING VOICE CONVERSION: RETHINKING CONCATENATION-BASED APPROACH FOR ONE-SHOT SINGING VOICE CONVERSION 笔记 发现问题: 在any-to-any的转换中,由于内容和说话人音色的解耦不足,导致源说话人的音色部分仍保留在转换后的音频中&#x…...

机器学习(1)--数据可视化
文章目录 数据可视化作用可视化方法实现可视化 总结 数据可视化 数据可视化是将数据以图形、图像、动画等视觉形式表示出来,以便人们能够更直观地理解、分析和交流数据中的信息。 作用 一个整理的好好的数据,我们为什么要将其可视化呢?将它…...

docker部署Prometheus、Grafana
docker部署Prometheus 1、 拉取prometheus镜像 docler pull prom/prometheus 遇到问题:注意下科学上网。 2、将prometheus配置文件放在外面管理 prometheus.yml global:scrape_interval: 15sevaluation_interval: 15salerting:alertmanagers:- static_configs:-…...

5.mysql多表查询
MYSQL多表查询 MYSQL多表查询1.多表关系笛卡尔积 2. 多表查询概述2.1 内连接2.2 外连接2.3自连接联合查询union ,union all 2.4子查询2.4.1标量子查询2.4.2列子查询2.4.3行子查询2.4.4表子查询 MYSQL多表查询 create table student(id int auto_increment primary …...

【前端面试】挖掘做过的nextJS项目(上)
为什么使用nextJS 需求: 快速搭建宣传官网 1.适应pc、移动端 2.基本的路由跳转 3.页面渲染优化 4.宣传的图片、视频资源的加载优化 5.seo优化 全栈react web应用、 tailwind css原子工具的支持,方便书写响应式ui app router(React 服务器组件)支持服务器渲…...

【Unity-UGUI】UGUI知识汇总
目录 前言1 UGUI系统原理2 事件系统2.1 EventSystem2.2 InputModules2.3 Raycasters2.4 协作 3 UGUI系统的组件3.1 Image和RawImage3.2 Mask和RectMask2D 扩展UI穿透问题 前言 记录一些最近学到的有关UGUI的知识。 参考 知乎:6千字带你入门UGUI源码 书籍ÿ…...