【C++】—— list 模拟实现

【C++】—— list 模拟实现

  • 1 list 基础结构
  • 2 默认构造
  • 3 迭代器
    • 3.1 整体框架
    • 3.2 成员函数
    • 3.3 begin() 与 end() 的实现
    • 3.4 operator-> 的实现
    • 3.5 const 迭代器
      • 3.5.1 const 迭代器为什么命名 const_iterator
      • 3.5.2 const 迭代器的实现
      • 3.5.3 合并两个迭代器
  • 4 源码

1 list 基础结构

   l i s t list list 的底层就是我们之前学过的双向链表,它由一个哨兵位头结点 _pHead记录链表长度_size 组成。
  而链表中的每个节点都是由两个自身类型指针一个存储数据的变量组成的。因此,我们不仅要定义链表的类,还要定义节点的类

  下面是 l i s t list list 的成员变量和整体框架

namespace my_list
{template<class T>struct ListNode{ListNode* _prev;ListNode* _next;T _val;//默认构造ListNode(const T& val = T()):_prev(nullptr),_next(nullptr),_val(val){}};template<class T>class list{typedef ListNode<T> Node;//给节点重命名//成员函数···private:Node* _pHead;//哨兵位头结点指针size_t _size;}
}

  因为我们要频繁访问节点,因此我们直接用 s t r u c t struct struct 定义节点,其成员变量默认全公有
  
  

2 默认构造

  我们先写一个简单的无参默认构造出来。

  默认构造可以直接这样写吗?

list():_pHead(nullptr),_size(0)
{}

  不可以的,因为双向链表有个哨兵位即使链表中没有任何数据,头节点指针也是指向哨兵位,而不是空,所以我们应创建哨兵位,并将其初始化
  而哨兵位的前驱指针 _ p r e v prev prev 和后继指针 _ n e x t next next,因为整个链表只有它自己,它的前一个和后一个节点都是自己,因此 _ p r e v prev prev 和 _ n e x t next next 都是指向哨兵位自己

list()
{_pHead = new Node;_pHead->_next = _pHead;_pHead->_prev = _pHead;_size = 0}

  其是不仅仅是无参的构造,所有的构造函数第一步都是初始化哨兵位,因此我们不妨单独写一个函数出来

list()
{CreateHead()}void CreateHead()
{_pHead = new Node;_pHead->_next = _pHead;_pHead->_prev = _pHead;_size = 0;
}

  
  这里有个问题就是CreateHead()是非 c o n s t const const 成员函数,那定义 c o n s t const const 成员是否还能来调用呢?答案自然是可以的因为 c o n s t const const 变量在定义的时候是不具有 c o n s t const const 属性的,定义完成之后才有。比如说:

//如果在定义之前就具有const属性,那么n就无法赋值
//const变量只有在定义时可以被赋值
const int n = 10;
const list<int> l1;

  
  

3 迭代器

  要模拟实现 l i s t list list迭代器的实现是其中的重中之重。
  前面我们模拟实现 s t r i n g string string v e c t o r vector vector,他们的迭代器都是原生指针,他们的原生指针完美符合迭代器的所有要求。其本质是因为他们底层物理空间是连续的。
  但 l i s t list list 不像 s t r i n g string string v e c t o r vector vector 那样天生丽质,它的底层结构是一个一个节点,并不连续,无法满足迭代器的要求(比如 ++,我们希望的是迭代器跳到下一个节点,如果使用原生指针,因为不是连续的物理空间,当前节点 ++ 大概率是个野指针)。
  但没关系, l i s t list list 可以通过封装,通过运算符重载,来满足迭代器的要求。
  

3.1 整体框架

  迭代器其本身就是模拟指针的行为,既然节点的原生指针无法满足迭代器的要求,我们对节点指针进行封装,通过运算符重载让其满足迭代器的需求

template<class T>
struct ListIterator
{typedef ListIterator<T> Self;//给自身类(迭代器)重命名,短一点方便typedef ListNode<T> Node;//节点重命名//成员变量:节点的指针Node* pNode;//成员函数//··· };

  因为待会链表中要大量访问成员变量,我们直接用默认全公有的 s t r u c t struct struct
  

到现在,我们一共实现了三个类,为什么要实现三个类呢?我们先把每个类的作用过一遍:

  • class list:链表这个类是链表的基本结构,指向哨兵位的头结点,管理这整个链表
  • struct ListNode:节点这个类是因为链表中每个节点都是自定义类型,每个数据都是存在一个独立的结构体里面
  • struct ListIterator:迭代器这个类,遍历整个链表本来是用节点的指针,但是节点的指针是不符合我们的预期,我们希望有一个迭代器统一的方式进行遍历,因此我们用一个结构去封装节点的指针,封装以后通过重载运算符使节点的指针能达到迭代器那样的行为

  
  

3.2 成员函数

  有了迭代器这个类,我们就可以运用运算符重载满足迭代器的行为啦

template<class T>
struct ListIterator
{typedef ListIterator<T> Self;typedef ListNode<T> Node;//成员变量Node* pNode;//解引用T& operator*(){return pNode->_val;}//前置++Self& operator++(){pNode = pNode->_next;return *this;}//后置++Self& operator++(int){Self tmp = *this;pNode = pNode->_next;return tmp;}//前置--Self& operator--(){pNode = pNode->_prev;return *this;}//后置--Self& operator--(int){Self tmp = *this;pNode = pNode->_prev;return tmp;}//不等于bool operator!=(const Self& x){return pNode != x.pNode;}//等于bool operator==(const Self& x){return pNode == x.pNode;}
}

  
  

3.3 begin() 与 end() 的实现

  迭代器的基本行为实现了,我们也可以在list类中实现begin()end()等函数了

  begin()函数是返回第一个迭代器,我们要构造第一个位置的迭代器,那怎么构造呢?我们用第一个节点的指针,即_pHead->_next,就能构造第一个迭代器,但现在ListIterator类中还缺少一个构造函数

//默认构造
ListIterator(Node* p = nullptr):pNode(p)
{}//拷贝构造
ListIterator(const ListIterator& x)
{pNode = x.pNode;
}

  其实上述拷贝构造可以不用实现,编译器会自己生成一个拷贝构造,完成浅拷贝,指向链表的节点。
  这里不要认为有指针指向资源就要自己实现深拷贝,而是看指针所指向的资源是不是属于自己的。像 s t r i n g string string v e c t o r vector vector 那些就需要自己实现深拷贝,但迭代器指向的是链表的资源,并不是迭代器自己的,并且迭代器本身的目标就是指向链表的节点,以此来访问遍历链表,因此浅拷贝即可。
  所以赋值重载析构与不需要写
  
  现在,万事具备只欠东风,我们还要在list类中将ListIterator类 t y p e d e f typedef typedef i t e r a t o r iterator iterator

template<class T>
class list
{typedef ListNode<T> Node;typedef ListIterator<T> iterator;//重命名为iterator//成员函数···private:Node* _pHead;size_t _size;
}

  下面是begin()的实现

iterator begin()
{iterator it(_pHead->_next);return it;
}

  上述是有名对象的写法,我们可以用匿名对象的写法

iterator begin()
{	return iterator(_pHead->_next);
}

  
  甚至我们可以用隐式类型转换,直接传指针就好啦

  迭代器本身就是节点的指针,指针节点指针本身不满足迭代器的那些需求,所以我们才用一个类把他封装一层,用重载运算符使其达到迭代器的要求。

  

iterator begin()
{return _pHead->_next;
}

  end()最后一个数据的下一个位置,就是哨兵位的头结点

iterator end()
{return _pHead;
}

  
  

3.4 operator-> 的实现

  既然迭代器模拟的是指针的行为,那它还要实现 operator->
  什么情况下用到 -> 运算符呢?

  现在我们定义一个类型 AA,链表中存储的数据是 AA 类型,我们想依次遍历链表,打印每个节点 AA 中的两个成员变量

struct AA
{int _a1 = 1;int _a2 = 2;
}void test1()
{list<AA> lta;lta.push_back(AA());lta.push_back(AA());lta.push_back(AA());list<AA>::iterator it = lta.begin();while (it != lta.end()){cout << (*it)._a1 << " " << (*it)._a2 << endl;cout << it->_a1 << " " << it->_a2 << endl;}
}

  上述(*it)._a1it->_a1的写法是等价的,但现在还没重载->运算符

  下面是operator->的实现方式

T* operator->()
{return &(pNode->_val);
}

  operator->的实现方式非常奇怪,返回的是T*
  
  其实这里省略了一个 ->,因为太难看了,为了可读性省略了一个 ->

cout << it->_a1 << " " << it->_a2 << endl;//本质是这样的
cout << it->->_a1 << " " << it->->_a2 << endl;

  
  第一个->是运算符重载出来的,第二个则是普通的->
  本质是这样的:

cout << it.operator->()->_a1 << " " << it.operator->()->_a2 << endl;cout << &(pNode->_val)->_a1 << " " << &(pNode->_val)->_a2 << endl;

  
  

3.5 const 迭代器

3.5.1 const 迭代器为什么命名 const_iterator

  首先问大家一个问题: c o n s t const const 迭代器为什么是const_iterator,而不是const iterator

   c o n s t const const 迭代器是自身不能修改还是指向的内容不能修改呢?
  就和指针一样,指针的 c o n s t const const 有两个,一个在 * 之前,一个在 * 之后

T* const ptr1//指针本身不能修改
const T* ptr2//指向的内容不能修改

  
  如果是 const iterator ,const 直接修饰一个变量,就是这个变量本身不能修改,即迭代器本身不能修改,而我们 c o n s t const const 迭代器是要指向的内容不能修改,所以const_iterator更合适
  
  

3.5.2 const 迭代器的实现

  那我们如何让迭代器指向的内容不能修改呢?

  迭代器修改我们指向的内容是怎么修改的?通过operator*operator->,那我们在其返回值上加上 c o n s t const const 就不能修改了

const T& operator*()
{return pNode->_val;
}const T* operator->()
{return &(pNode->_val);
}

  那我们就再自己实现一个 c o n s t const const 迭代器的封装吧

template<class T>
struct const_ListIterator
{typedef const_ListIterator<T> Self;typedef ListNode<T> Node;Node* pNode;const_ListIterator(Node* p = nullptr):pNode(p){}const T& operator*(){return pNode->_val;}const T* operator->(){return &(pNode->_val);}Self& operator++(){pNode = pNode->_next;return *this;}Self& operator++(int){Self tmp = *this;pNode = pNode->_next;return tmp;}Self& operator--(){pNode = pNode->_prev;return *this;}Self& operator--(int){Self tmp = *this;pNode = pNode->_prev;return tmp;}bool operator!=(const Self& x){return pNode != x.pNode;}bool operator==(const Self& x){return pNode == x.pNode;}
};

  

3.5.3 合并两个迭代器

  上面我们再重新封装了一个const_iterator,基本满足了需求,但是代码太冗余了,除了operator*operator-> 的返回值类型不一样,其他代码全是一样的,有什么办法将他们合二为一呢?

  我们来看一下库中是怎么实现的

在这里插入图片描述

  库中的__list_iterator类,用了三个模板参数,新增了 RefPtr 两个参数。

  在 l i s t list list 类中,将__list_iterator<T, T&, T*> t y p e d e f typedef typedef i t e r a t o r iterator iterator,将__list_iterator<T, const T&, const T*> t y p e d e f typedef typedef c o n s t const const_ i t e r a t o r iterator iterator
  也就是说 i t e r a t o r iterator iterator R e f Ref Ref 参数即 T& P t r Ptr Ptr 即参数 T* c o n s t const const_ i t e r a t o r iterator iterator R e f Ref Ref参数即 c o n s t const const T& P t r Ptr Ptr 参数即 c o n s t const const T*

operator* 举例:
  T& 传给RefRefreferenceoperator*的返回值是reference,替换过来operator*的返回值就是 T&
c o n s t const const_ i t e r a t o r iterator iterator
   c o n s t const const T& 传给 R e f Ref Ref,替换过来operator*的返回值就是 c o n s t const const T&

  其实,这种写法与上面我们自己实现两个类模板并没有本质区别,他们实例化出来都是两个不同的类。不同的是第一种写法是我们自己写了两个不同的类,而第二种是我们通过控制模板参数让编译器实例化出两个不同的类,把我们干的活交给了编译器
  
代码如下:

template<class T, class Ref, class Ptr>
struct ListIterator
{typedef ListIterator<T, Ref, Ptr> Self;typedef ListNode<T> Node;Node* pNode;ListIterator(Node* p = nullptr):pNode(p){}Ref operator*(){return pNode->_val;}Ptr operator->(){return &(pNode->_val);}Self& operator++(){pNode = pNode->_next;return *this;}Self& operator++(int){Self tmp = *this;pNode = pNode->_next;return tmp;}Self& operator--(){pNode = pNode->_prev;return *this;}Self& operator--(int){Self tmp = *this;pNode = pNode->_prev;return tmp;}bool operator!=(const Self& x){return pNode != x.pNode;}bool operator==(const Self& x){return pNode == x.pNode;}
};

  
  

4 源码

对于 l i s t list list 的其他成员函数,与前面的 s t r i n g string string v e c t o r vector vector 实现起来都大同小异,这里就不再赘述了,我们直接看源码

#pragma once
#include<iostream>
#include<assert.h>using namespace std;namespace my_list
{template<class T>struct ListNode{ListNode* _prev;ListNode* _next;T _val;ListNode(const T& val = T()):_prev(nullptr),_next(nullptr),_val(val){}};template<class T, class Ref, class Ptr>struct ListIterator{typedef ListIterator<T, Ref, Ptr> Self;typedef ListNode<T> Node;Node* pNode;ListIterator(Node* p = nullptr):pNode(p){}Ref operator*(){return pNode->_val;}Ptr operator->(){return &(pNode->_val);}Self& operator++(){pNode = pNode->_next;return *this;}Self& operator++(int){Self tmp = *this;pNode = pNode->_next;return tmp;}Self& operator--(){pNode = pNode->_prev;return *this;}Self& operator--(int){Self tmp = *this;pNode = pNode->_prev;return tmp;}bool operator!=(const Self& x){return pNode != x.pNode;}bool operator==(const Self& x){return pNode == x.pNode;}};template<class T>class list{typedef ListNode<T> Node;public:typedef ListIterator<T, T&, T*> iterator;typedef ListIterator<T, const T&, const T*> const_iterator;list():_pHead(nullptr), _size(0){CreateHead();}list(int n, const T& value = T()){CreateHead();while (n--){push_back(value);}}template <class Iterator>list(Iterator first, Iterator last){CreateHead();Iterator it = first;while (it != last){push_back(*it);++it;}}list(const list<T>& l){CreateHead();const_iterator it = l.begin();while (it != l.end()){push_back(*it);++it;}}list<T>& operator=(list<T> l){swap(l);return *this;}~list(){clear();delete _pHead;_pHead = nullptr;}iterator begin(){return _pHead->_next;}iterator end(){return _pHead;}const_iterator begin() const{return _pHead->_next;}const_iterator end() const{return _pHead;}size_t size()const{return _size;}bool empty()const{return _size == 0;}T& front()                                                                                                {return _pHead->_next->_val;}T& back(){return _pHead->_prev->_val;}const T& front() const{return _pHead->_next->_val;}const T& back() const{return _pHead->_prev->_val;}void push_back(const T& val){Node* p = new Node(val);p->_next = _pHead;_pHead->_prev->_next = p;p->_prev = _pHead->_prev;_pHead->_prev = p;++_size;}void pop_back(){Node* p = _pHead->_prev;p->_prev->_next = _pHead;_pHead->_prev = p->_prev;delete p;--_size;}void push_front(const T& val){Node* p = new Node(val);p->_next = _pHead->_next;p->_prev = _pHead;_pHead->_next = p;p->_next->_prev = p;++_size;}void pop_front(){Node* p = _pHead->_next;_pHead->_next = p->_next;p->_next->_prev = _pHead;delete p;--_size;}iterator insert(iterator pos, const T& val){Node* p = new Node(val);p->_next = pos.pNode;p->_prev = pos.pNode->_prev;pos.pNode->_prev->_next = p;pos.pNode->_prev = p;++_size;return pos;}iterator erase(iterator pos){assert(pos != end());iterator ret = pos.pNode->_next;pos.pNode->_prev->_next = pos.pNode->_next;pos.pNode->_next->_prev = pos.pNode->_prev;delete pos.pNode;--_size;return ret;}void clear(){iterator it = begin();while (it != end()){iterator cur = it++;delete cur.pNode;}_pHead->_next = _pHead;_pHead->_prev = _pHead;_size = 0;}void swap(list<T>& l){std::swap(_pHead, l._pHead);std::swap(_size, l._size);}private:Node* _pHead;size_t _size;void CreateHead(){_pHead = new Node;_pHead->_next = _pHead;_pHead->_prev = _pHead;_size = 0;}};
}template<class Container>
void print_container(const Container& v)
{auto it = v.begin();while (it != v.end()){cout << *it << " ";++it;}cout << endl;
}

  
  
  
  
  


  好啦,本期关于 l i s t list list 的知识就介绍到这里啦,希望本期博客能对你有所帮助。同时,如果有错误的地方请多多指正,让我们在 C++ 的学习路上一起进步!

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

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

相关文章

计算机毕业设计选题推荐-校园车辆管理系统-Java/Python项目实战(亮点:数据可视化分析、账号锁定)

✨作者主页&#xff1a;IT研究室✨ 个人简介&#xff1a;曾从事计算机专业培训教学&#xff0c;擅长Java、Python、微信小程序、Golang、安卓Android等项目实战。接项目定制开发、代码讲解、答辩教学、文档编写、降重等。 ☑文末获取源码☑ 精彩专栏推荐⬇⬇⬇ Java项目 Python…

ElementUI大坑Notification修改样式

默认<style lang"scss" scoped>局部样式&#xff0c;尝试用deep透传也无效 实践成功方法&#xff1a;单独写一个style <style> .el-notification{position: absolute !important;top: 40% !important;left: 40% !important; } </style> 也支持自…

开放式耳机排行榜前十名?分享四款高性价比的开放式蓝牙耳机

开放式耳机并不一定要选价格贵的才好&#xff0c;而是应该按照个人需求来选择合适的开放式耳机产品&#xff0c;适合自己的才是最好。而且开放式耳机的价格区间也很广&#xff0c;从几十元到上千元不等&#xff0c;在每个价位区间里都有属于每个价位区间的高性价比耳机。选择耳…

C语言-结构体-详解

博客主页&#xff1a;【夜泉_ly】 本文专栏&#xff1a;【C语言】 欢迎点赞&#x1f44d;收藏⭐关注❤️ C语言-结构体-详解 1.前言2.结构体类型2.1声明2.2变量的创建与初始化2.3访问2.4匿名结构体类型 3.结构体内存对齐3.1对齐规则3.2示例 1.前言 在C语言中&#xff0c;除了整…

Python 从入门到实战20(函数的返回值等)

我们的目标是&#xff1a;通过这一套资料学习下来&#xff0c;通过熟练掌握python基础&#xff0c;然后结合经典实例、实践相结合&#xff0c;使我们完全掌握python&#xff0c;并做到独立完成项目开发的能力。 上篇文章我们讨论了函数的参数。今天我们继续学习一下函数的返回…

SysML图例-农业无人机

DDD领域驱动设计批评文集>> 《软件方法》强化自测题集>> 《软件方法》各章合集>>

数据结构—双向链表

结构 带头链表里的头结点&#xff0c;实际为“哨兵位”&#xff0c;哨兵位结点不存储任何有效元素&#xff0c;只是站在这里“放哨 的” 实现双向链表 List.h #pragma once#include<stdio.h> #include<stdlib.h> #include<assert.h> #include<stdbool…

Mac 上,终端如何开启 proxy

前提 确保你的浏览器可以访问 google&#xff0c;就是得先有这个能力 步骤 查看网络的 http/https 还有 socks5 的 port配置 .zshrc 查看 port 点击 wifi 设置 以我的为例&#xff0c;我的 http/https 都是 7890&#xff0c; socks5 是 7891 查看代理的port 以我的软件…

文件误删除后的数据救援实战指南

在数字化时代&#xff0c;文件误删除成为了许多用户心头挥之不去的阴影。无论是手误点击了“删除”键&#xff0c;还是系统崩溃导致的数据丢失&#xff0c;文件一旦从我们的视线中消失&#xff0c;往往伴随着重要信息的流失和工作的中断。本文将深入探讨文件误删除的现象&#…

打造高效实时数仓,从Hive到OceanBase的经验分享

本文作者&#xff1a;Coolmoon1202&#xff0c;大数据高级工程师&#xff0c;专注于高性能软件架构设计 我们的业务主要围绕出行领域&#xff0c;鉴于初期采用的数据仓库方案面临高延迟、低效率等挑战&#xff0c;我们踏上了探索新数仓解决方案的征途。本文分享了我们在方案筛选…

Java开发安全及防护

目录 一、开发安全 二、XSS介绍及防范措施 2.1何为XSS 2.2XSS分类 2.3常用方法 三、SQL注入介绍及防范措施 3.1何为SQL注入 3.2常用方法 四、重放介绍及防范措施 4.1何为重放 4.2常用方法 一、开发安全 在学习安全之前&#xff0c;我们首先学习漏洞&#xff0c;知道…

视频格式转为mp4(使用ffmpeg)

1、首先安装ffmpeg&#xff0c;下载链接如下 https://www.gyan.dev/ffmpeg/builds/packages/ffmpeg-6.1.1-full_build.7z 安装后确保ffmpeg程序加到PATH路径里&#xff0c;cmd执行ffmpeg -version出现下图内容表示安装成功。 2、粘贴下面的脚本到文本文件中&#xff0c;文件后缀…

【Python笔记】PyCharm大模型项目环境配置

一、PyCharm创建新项目 二、更新pip版本 ...>python.exe -m pip install --upgrade pip 三、生成所需requirements配置文件 ...>pip freeze > requirements.txt 四、安装所需组件requirements.txt ...>pip install -r requirements.txt

基于代理的分布式身份管理方案

目的是使用分布式的联合计算分发去替换掉区块链中原有的类第三方可信中心的证书机制&#xff0c;更加去中心化。 GS-TBK Group Signatures with Time-bound Keys. CS-TBK 算法 Complete subtree With Time-bound Keys&#xff0c;该算法是用来辅助检测用户的签名是否有效&…

微服务_入门2

文章目录 一、Feign 一、Feign 来看我们以前利用RestTemplate发起远程调用的代码&#xff1a; 存在下面的问题&#xff1a; 代码可读性差&#xff0c;编程体验不统一参数复杂URL难以维护&#xff08;有时候访问一个页面所携带的参数是非常多的&#xff09; Feign是一个声明…

CSS——网格布局(display: grid)之上篇

CSS——网格布局&#xff08;display: grid&#xff09; 前面介绍了弹性布局&#xff0c;今天我们介绍一下网格布局。 什么是网格布局 CSS网格布局&#xff08;CSS Grid Layout&#xff09;是一种用于创建复杂网页布局的系统&#xff0c;它允许开发者以二维系统&#xff08;…

双三次插值及MATLAB实现

一、双三次插值的概念 双三次插值&#xff08;Bicubic interpolation&#xff09;&#xff0c;又叫双立方插值。在数值分析这个数学分支中&#xff0c;双三次插值是二维空间中最常用的插值方法。在这种方法中&#xff0c;函数f在点 (x0 ,y0) 的值不仅考虑其直接邻接点对其的影响…

Leetcode—1137. 第 N 个泰波那契数【简单】

2024每日刷题&#xff08;160&#xff09; Leetcode—1137. 第 N 个泰波那契数 记忆化搜索实现代码 class Solution { public:int tribonacci(int n) {int zero 0;int one 1;int two 1;if(n 0) {return zero;}if(n 1) {return one;}if(n 2) {return two;}int ans 0;fo…

MATLAB、FPGA、STM32中调用FFT计算频率、幅值及相位差

系列文章目录 文章目录 系列文章目录前言MATLABSTM32调用DSPSTM32中实现FFT关于初相位 FPGA 前言 最近在学习如何在STM32中调用FFT MATLAB 首先对FFT进行一下说明&#xff0c;我们输入N个点的数据到FFT中&#xff0c;FFT会返回N个点的数据&#xff0c;这些数据都是复数&#…

【ACM出版】第三届人工智能与智能信息处理国际学术会议(AIIIP 2024,10月25-27)

第三届人工智能与智能信息处理国际学术会议&#xff08;AIIIP 2024&#xff09; 2024 3rd International Conference on Artificial Intelligence and Intelligent Information Processing 中国-天津 | 2024年10月25-27日 | 会议官网&#xff1a;www.aiiip.net 官方信息 会议…