半边数据结构学习

半边数据结构学习

  • 一、网格数据结构
  • 二、半边数据结构
    • 顶点(Vertex)
    • 半边(HalfEdge)
    • 面片(Face)
  • 三、OpenMesh 相关代码
    • 拓扑关联对象
    • 遍历
  • 四、OpenFilpper 相关代码
    • HoleInfo类
    • 孔洞检测
    • 孔洞信息
    • HoleFiller类
    • 孔洞补全


一、网格数据结构

对于表面网络来说,其关键在于拓扑,也就是曲面是如何表达的。拓扑的不同造就了不同的数据结构和标准,其进行网格查询和编辑的性能也不同。

常见的网格数据结构有基于面的数据结构、基于边的数据结构、半边数据结构、有向边数据结构等。这里主要结合OpenMesh以及OpenFlipper学习一下半边数据结构。

二、半边数据结构

在这里插入图片描述

每个边分为两个半边,每个半边都是一个有向边,方向相反。如果一个边被两个面片公用,则每个面片都能各自拥有一个半边。如果一个边仅被一个面片占用(边界边),则这个面片仅拥有该边的其中一个半边,另一个半边为闲置状态。每一条半边仅存储它的起点指针。

那么是否可以通过边的闲置状态来定位网格中的孔洞或边界?

半边数据结构仅支持流形网络。

计算机图形学上,通常说的流形是一种几何模型表面(但不是所有的),即二维流形,对应拓扑流形。如果网格的每个边最多被两个面片共用,那么这个网格就是流形网络,否则称为非流形网络。

半边数据结构的三个重要的数据结构——顶点、半边、面片。

顶点(Vertex)

包含出半边(OutgoingHalfedge)的指针或索引。
在半边数据结构中的点储存着其位置和以其为起始点的半边的指针。

当在点上存在有多条半边相连,可以指向任意一半边,可以通过以下查询方式遍历所有半边

struct HE_vert  {  float x,y,z;  HE_edge* edge;  // one of the half-edges emantating from the vertex  指向任意一个以它为出发点的半边};

半边(HalfEdge)

包含起点(StartVertex)、邻接面(AdjacentFace)、下一条半边(NextHalfedge)、相反边(opposite)的指针或索引。

注意:有的实现方式是将指向半边的终点。

struct HE_edge  {  HE_vert* vert;   // vertex at the start of the half-edge  	指向半边的出发点	HE_edge* pair;   // oppositely oriented adjacent half-edge   指向相反的相邻半边(也称twin)HE_edge* next;   // next half-edge around the face  			指向后一个半边HE_face* face;   // face the half-edge borders  				指向半边相邻的面片};

面片(Face)

包含一条起始边(FirstHalfedge)的指针或索引对于一个半边数据结构的简单形式,一个面仅仅需要储存一个围绕它的边的指针,在一些特定场合可能要求我们储存比如材质和法向一类的信息。和上面一样,虽然有很多边围绕着面,我们只需要储存其中一条,而无所谓是哪一条。

struct HE_face  {  HE_edge* edge;  // one of the half-edges bordering the face  指向任意一个环绕它的半边};

顶点可能有两条或以上的出半边,而顶点的数据表达只有一条出半边,那这条出半边是哪一条?半边的下一条半边又是哪一条?面片的起始半边又是哪一条?通过某个网格的数据结构图能看得出这些信息吗?

事实上,半边数据结构的网格的构建通常是通过面列表来创建的,也就是说,正常的构建半边数据结构网格是通过一个一个面片的添加来构建的。

所以面的添加顺序就决定了点边面结构的信息,添加面的方法通常是addFace(a,b,c,…),a,b,c…参数是该面片按其某条环路顺序排列的顶点的指针或索引。注意,环路可以是顺时针或者逆时针,决定了该面片的方向(法向量的方向)。


三、OpenMesh 相关代码

OpenMesh就是使用的半边数据结构。

拓扑关联对象

// Type declarations for handles:	句柄类型声明:
OpenMesh::VertexHandle verH;       // 顶点句柄声明
OpenMesh::FaceHandle facH;         // 面句柄声明
OpenMesh::HalfedgeHandle hedH, hedH_n, hedH_p;  // 半边句柄声明
OpenMesh::Vec3d point;             // 三维向量,用于存储顶点坐标// Half-edge operations:	半边操作:
half-edge → vert: verH = mesh.to_vertex_handle(hedH);			// 从半边获取顶点句柄
half-edge→ pair→vert:verH = mesh.from_vertex_handle(hedH);		// 从对向半边获取顶点句柄
half-edge→ next:hedH_n = mesh.next_halfedge_handle(hedH);		// 获取当前面中下一条半边的句柄
half-edge→pair:hedH_p = mesh.opposite_halfedge_handle(hedH);	// 获取当前半边的对向半边的句柄
half-edge → face: facH = mesh.face_handle(hedH);				// 获取包含给定半边的面句柄
face → half-edge: hedH = mesh.halfedge_handle(facH);			// 获取给定面的一条半边句柄
vert → half-edge: hedH = mesh.halfedge_handle(verH);			// 获取从给定顶点出发的一条半边句柄
vert coordinates: point = mesh.point(verH);						// 获取与顶点句柄相关联的顶点的三维坐标

遍历

// vertex iterator
typedef OpenMesh::TriMesh_ArrayKernelT<> MyMesh;	// 定义网格类型
MyMesh mesh;
OpenMesh::VertexHandle verH;						// 定义顶点句柄
MyMesh::VertexIter vit;								// 定义顶点迭代器
OpenMesh::Vec3d p;
int valence;										// 定义存储顶点度的变量
OpenMesh::IO::read_mesh(mesh, "cube.off")
for (vit = mesh.vertices_begin(); vit != mesh.vertices_end(); vit++)	// 遍历所有顶点
{verH = *vit;					// 获取当前迭代器指向的顶点句柄p = mesh.point(verH);			// 获取顶点坐标valence = mesh.valence(verH);	// 获取顶点的度(连接的边数)cout << p[0] << " " << p[1] << " " << p[2] << " " << valence << endl;	// 输出顶点坐标和度数
}
// face iteratortypedef OpenMesh::TriMesh_ArrayKernelT<> MyMesh;
MyMesh mesh;
OpenMesh::FaceHandle facH;			// 定义面句柄
MyMesh::FaceIter fit;				// 定义面迭代器
int nvert;							// 定义存储顶点数的变量
OpenMesh::IO::read_mesh(mesh, "cube.off")
for (fit = mesh.faces_begin(); fit != mesh.faces_end(); fit++)		// 遍历所有面
{facH = *fit;					// 获取当前迭代器指向的面句柄nvert = mesh.valence(facH);		// 获取面的顶点数(面的度)cout << "Number of vertices = " << nvert << endl;
}
// edge iteratortypedef OpenMesh::TriMesh_ArrayKernelT<> MyMesh;
MyMesh mesh;
OpenMesh::EdgeHandle edgH;		// 定义边句柄
MyMesh::EdgeIter eit;			// 定义边迭代器
float len, angle;				// 定义存储边长度和二面角的变量
OpenMesh::IO::read_mesh(mesh, "cube.off")
for (eit = mesh.edges_begin(); eit != mesh.edges_end(); eit++)		// 遍历所有边
{edgH = *eit;len = mesh.calc_edge_length(edgH);			// 计算边的长度angle = mesh.calc_dihedral_angle(edgH);		// 输出边的长度和二面角cout << "Edge length = " << len <<" Dihedral angle = " << angle << endl;
}

半面及二面角 :一条直线把平面分成两个部分,其中的每一部分都称为半平面。从一条直线出发的两个半平面所组成的图形称为二面角。这条直线称为二面角的棱,这两个半平面称为二面角的面。


四、OpenFilpper 相关代码

在三维重建中,建模的网格尝尝由于遮挡等原因产生孔洞,导致其完整性降低,因此,常通过网格孔洞修复来得到一个完整的结构(但一般只能用于简单网格)。接下来看一下OpenFilpper中是怎么查找和修复孔洞的。

HoleInfo类

在这里插入图片描述

孔洞检测

 template< class MeshT >void HoleInfo< MeshT >::getHoles(){// Property for the active mesh to mark already visited edges	// 边界搜索的属性句柄,用于标记已访问的边界边OpenMesh::EPropHandleT< bool > boundary_search;// Add a property to the mesh to store original vertex positionsmesh_->add_property( boundary_search, "Boundary search" );		// Initialize Property	// 先都初始化为false,表示未被访问typename MeshT::EdgeIter e_it, e_end=mesh_->edges_end();for (e_it=mesh_->edges_begin(); e_it!=e_end; ++e_it) {mesh_->property(  boundary_search , *e_it ) = false;}holes_.clear();for (e_it=mesh_->edges_begin(); e_it!=e_end; ++e_it) {// Skip already visited edges		// 跳过已访问的边if ( mesh_->property(  boundary_search , *e_it ) )continue;// Check only boundary edges		// 只处理边界边if ( !mesh_->is_boundary(*e_it))continue;						// Get boundary halfedge			// 获取边界的半边typename MeshT::HalfedgeHandle hh = mesh_->halfedge_handle( *e_it, 0 );if ( ! mesh_->is_boundary( hh ) )hh = mesh_->opposite_halfedge_handle( hh );typename MeshT::Point center(0,0,0);Hole currentHole;					// 初始化孔洞信息// Collect boundary edges			// 收集边界边typename MeshT::HalfedgeHandle  ch = hh;do {currentHole.push_back( mesh_->edge_handle(ch) );// 计算孔洞中心center += mesh_->point( mesh_->from_vertex_handle(ch) );		mesh_->property(  boundary_search , mesh_->edge_handle(ch) ) = true;//check number of outgoing boundary HEH's at Vertex	// 检查顶点的出边数目int c = 0;typename MeshT::VertexHandle vh = mesh_->to_vertex_handle(ch);for ( typename MeshT::VertexOHalfedgeIter voh_it(*mesh_,vh); voh_it.is_valid(); ++voh_it)if ( mesh_->is_boundary( *voh_it ) )c++;if ( c >= 2){	// 如果顶点有多于两条出边,选择下一个出边typename MeshT::HalfedgeHandle  op = mesh_->opposite_halfedge_handle( ch );typename MeshT::VertexOHalfedgeIter voh_it(*mesh_,op);ch = *(++voh_it);} elsech = mesh_->next_halfedge_handle( ch );} while ( ch != hh );center /= currentHole.size();		// 计算孔洞中心bool isHole = true;				// 检查孔洞形状int err = 0;for (unsigned int i=0; i < currentHole.size(); i++){typename MeshT::HalfedgeHandle hh = mesh_->halfedge_handle( currentHole[i], 0 );if ( ! mesh_->is_boundary( hh ) )hh = mesh_->opposite_halfedge_handle( hh );typename MeshT::VertexHandle vh = mesh_->from_vertex_handle(hh);typename MeshT::Normal n = mesh_->normal( vh );typename MeshT::Point p = mesh_->point( vh );// 检查孔洞中每个点到孔洞中心的距离if ( (p - center).norm() < (p + n - center).norm()  ){//           isHole = false;//           break;err++;}}//   std::cerr << "Errors " << err << " Size " << hole.count << std::endl; if (isHole)		// 如果是孔洞,将其加入孔洞列表holes_.push_back(currentHole);}mesh_->remove_property( boundary_search);		// 结束时,移除boundary_search属性}

使用半边数据结构检测孔洞的步骤

  1. 标记边界边
    • 遍历网格的所有边,找出只有半边的边作为边界边。
  2. 遍历边界边
    • 对于每条边界边,获取与该边关联的半边。
  3. 跟踪半边环
    • 从一条边界边开始,可以通过半边数据结构相关函数沿着边界环遍历。
    • 遍历半边环的方法:
      • 使用 next_halfedge_handle() 函数从当前半边跳转到下一条边界上的半边。
      • 如果遇到顶点有多条出边的情况(表示顶点是网格的拐角),可以使用 opposite_halfedge_handle() 函数找到对称的半边,然后从中选择下一个出边。
  4. 收集孔洞边
    • 在遍历半边环的过程中,收集所有构成孔洞的边界边。可以使用一个列表或类似的数据结构来存储这些边的句柄或索引。
  5. 验证孔洞
    • 一旦收集完一条半边环上的所有边界边,可以进行一些验证步骤来确认它确实是一个孔洞,比如计算孔洞的几何中心,检查边界边是否按某种顺序排列等。
  6. 存储孔洞
    • 验证通过则将孔洞的边界边等信息存储在孔洞列表中。

孔洞信息

 template< class MeshT >void HoleInfo< MeshT >::getHolePostitionInfo(const int _index, typename MeshT::Normal& _holeNormal, typename MeshT::Point& _holeCenter) const{// 计算指定孔洞 _index 的中心位置和平均法线 _holeCenter = typename MeshT::Point(0.0,0.0,0.0);_holeNormal = typename MeshT::Normal(0.0,0.0,0.0);// Center of gravity of hole and an average normal at the hole boundaryfor ( size_t i = 0 ; i <  holes_[_index].size() ; ++i ) {const typename MeshT::HalfedgeHandle he = mesh_->halfedge_handle(holes_[_index][i],0);const typename MeshT::VertexHandle vh_to = mesh_->to_vertex_handle(he);_holeCenter += mesh_->point(vh_to);_holeNormal += mesh_->normal(vh_to);}_holeCenter /= typename MeshT::Scalar(holes_[_index].size());_holeNormal /= typename MeshT::Scalar(holes_[_index].size());_holeNormal.normalize();}template< class MeshT >void HoleInfo< MeshT >::getHoleInfo(const unsigned int _index, size_t& _edges, typename MeshT::Scalar& _diagonal, typename MeshT::Scalar& _boundaryLength) const{//获取指定孔洞 _index 的基本信息,包括边数、对角线长度和边界长度 if ( _index >= holes_.size() ) {std::cerr << "Invalid hole index " << _index << std::endl;return;}_boundaryLength = 0.0;typename MeshT::Point minCoord = typename MeshT::Point(std::numeric_limits<typename MeshT::Scalar>::max(),std::numeric_limits<typename MeshT::Scalar>::max(),std::numeric_limits<typename MeshT::Scalar>::max());typename MeshT::Point maxCoord = typename MeshT::Point(-std::numeric_limits<typename MeshT::Scalar>::max(),-std::numeric_limits<typename MeshT::Scalar>::max(),-std::numeric_limits<typename MeshT::Scalar>::max());for (size_t i = 0 ; i < holes_[_index].size() ; ++i) {_boundaryLength += mesh_->calc_edge_length(holes_[_index][i]);typename MeshT::Point pos = mesh_->point(mesh_->from_vertex_handle(mesh_->halfedge_handle(holes_[_index][i],0)));minCoord[0] = std::min(minCoord[0],pos[0]);minCoord[1] = std::min(minCoord[1],pos[1]);minCoord[2] = std::min(minCoord[2],pos[2]);maxCoord[0] = std::max(maxCoord[0],pos[0]);maxCoord[1] = std::max(maxCoord[1],pos[1]);maxCoord[2] = std::max(maxCoord[2],pos[2]);}_edges = holes_[_index].size();_diagonal = (maxCoord - minCoord).length();}template< class MeshT >std::vector< std::vector< typename MeshT::EdgeHandle > >* HoleInfo< MeshT >::holes(){//返回指向孔洞列表 holes_ 的指针return &holes_;}

HoleFiller类

在这里插入图片描述
在这里插入图片描述

孔洞补全

template< class MeshT >
void HoleInfo< MeshT >::fillHole(int _index, int _stages)
{if ((uint) _index > holes_.size()){			// 检查索引是否有效std::cerr << "Cannot fill hole. Index invalid." << std::endl;return;}// 如果filler为空,则创建一个新fillerif (filler_ == 0)filler_ = new HoleFiller< MeshT >(*mesh_);// 使用filler填补指定孔洞的第一个边界边filler_->fill_hole(holes_[_index][0], _stages);// 垃圾回收,清理无效数据mesh_->garbage_collection();// 清除边选择状态MeshSelection::clearEdgeSelection(mesh_);// 更新网格法线mesh_->update_normals();
}template< class MeshT >
void HoleInfo< MeshT >::fillHole(typename MeshT::EdgeHandle _eh, int _stages)
{if (filler_ == 0)filler_ = new HoleFiller< MeshT >(*mesh_);filler_->fill_hole(_eh, _stages);mesh_->garbage_collection();MeshSelection::clearEdgeSelection(mesh_);mesh_->update_normals();
}template< class MeshT >void HoleInfo< MeshT >::fillAllHoles(int _stages){if (filler_ == 0)filler_ = new HoleFiller< MeshT >(*mesh_);filler_->fill_all_holes( _stages );}
//=============================================================================
//
//  Fill a hole which is identified by one of its boundary edges.
//	通过其边界边填补一个被识别的孔洞。
//
//=============================================================================template< class MeshT >
void HoleFiller< MeshT >::fill_hole( EH _eh, int _stages )
{std::cerr << "  Stage 1 : Computing a minimal triangulation ... ";// 打印信息:阶段1:计算最小三角剖分// 记录最后一个顶点,用于选择新顶点typename MeshT::VertexHandle old_last_handle = *(--mesh_.vertices_end());// 如果没有边界边,则不是孔洞if ( ! mesh_.is_boundary( _eh ) )return;// 获取边界半边HH hh = mesh_.halfedge_handle( _eh, 0 );if ( ! mesh_.is_boundary( hh ) )hh = mesh_.opposite_halfedge_handle( hh );// 收集边界顶点和相对应的对向顶点boundary_vertex_.clear();opposite_vertex_.clear();HH ch = hh;do {boundary_vertex_.push_back( mesh_.from_vertex_handle( ch ) );opposite_vertex_.push_back( mesh_.to_vertex_handle(mesh_.next_halfedge_handle( mesh_.opposite_halfedge_handle( ch ) ) ) );// 计算顶点处的外向半边数量int c = 0;VH vh = mesh_.to_vertex_handle(ch);for ( typename MeshT::VertexOHalfedgeIter voh_it(mesh_, vh); voh_it.is_valid(); ++voh_it )if ( mesh_.is_boundary( *voh_it ) )c++;if ( c >= 2 ) {HH op = mesh_.opposite_halfedge_handle( ch );typename MeshT::VertexOHalfedgeIter voh_it(mesh_, op);ch = *(++voh_it);} elsech = mesh_.next_halfedge_handle( ch );} while ( ch != hh );int nv = boundary_vertex_.size();// 计算初始三角剖分所需的权重和连接信息w_.clear();w_.resize( nv, std::vector<Weight>( nv, Weight() ) );l_.clear();l_.resize( nv, std::vector<int>( nv, 0 ) );// 初始化边界上相邻顶点的权重为0for ( int i = 0; i < nv - 1; ++i )w_[i][i+1] = Weight( 0, 0 );// 动态规划计算最小权重的三角剖分for ( int j = 2; j < nv; ++j ) {#pragma omp parallel for shared(j, nv)for ( int i = 0; i < nv - j; ++i ) {Weight valmin;int argmin = -1;for ( int m = i + 1; m < i + j; ++m ) {Weight newval = w_[i][m] + w_[m][i+j] + weight( i, m, i+j );if ( newval < valmin ) {valmin = newval;argmin = m;}}w_[i][i+j] = valmin;l_[i][i+j] = argmin;}}// 实际填补孔洞。收集所有新生成的三角形和边界边hole_edge_.clear();hole_triangle_.clear();if ( fill( 0, nv - 1 ) ) {std::cerr << "ok\n";// 如果只需要一阶段,则返回if ( _stages <= 1 )return;std::cerr << "  Stage 2 : Fairing the filling ... ";// 打印信息:阶段2:平滑填充区域std::vector< FH > handles = hole_triangle_;// 对填补后的三角形进行平滑处理fairing(handles);// 标记所有新顶点为已选择状态typename MeshT::VertexIter old_end = ++typename MeshT::VertexIter(mesh_, old_last_handle);typename MeshT::VertexIter v_end = mesh_.vertices_end();for ( ; old_end != v_end; ++old_end )if ( !mesh_.status(*old_end).deleted() )mesh_.status(*old_end).set_selected( true );std::cerr << "ok\n";} elsestd::cerr << "Could not create triangulation" << std::endl;
}

使用 OpenFlipper 测试了一下,孔洞都能找到,但是需要进一步筛选。然后如果孔洞较大、不规则,其补全效果其实比较差。


先到这儿。


参考以推荐阅读:
1.半边数据结构&网格细分与简化
2.半边数据结构与OpenMesh中的处理
3.Polygon Mesh Processing 阅读笔记(2) 网格数据结构
4.The Half Edge Data Structure
5.Half-Edge Data Structures

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

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

相关文章

【MySQL系列】VARCHAR的底层存储

&#x1f49d;&#x1f49d;&#x1f49d;欢迎来到我的博客&#xff0c;很高兴能够在这里和您见面&#xff01;希望您在这里可以感受到一份轻松愉快的氛围&#xff0c;不仅可以获得有趣的内容和知识&#xff0c;也可以畅所欲言、分享您的想法和见解。 推荐:kwan 的首页,持续学…

python-亲和数(赛氪OJ)

[题目描述] 古希腊数学家毕达哥拉斯在自然数研究中发现&#xff0c;220 的所有真约数(即不是自身的约数)之和为&#xff1a; 1245101120224455110&#xff1d;284 。 而 284 的所有真约为 1 、 2 、 4 、 71 、 142 &#xff0c;加起来恰好为 220 。人们对这样的数感到很惊奇&a…

颐养优选元宇宙

颐养优选是一个专注于为中老年人提供高品质养老服务的品牌或平台。它通常涵盖了一系列服务和产品&#xff0c;旨在帮助老年人享受健康、舒适、有尊严的晚年生活。这些服务可能包括但不限于以下几个方面&#xff1a; ###健康管理 -**定期体检**&#xff1a;提供定期的身体健康检…

如何搞定美国TikTok直播网络?

在全球范围内&#xff0c;TikTok已经积累了超过30亿次的下载量&#xff0c;月活跃用户达到13亿以上&#xff0c;支持75种语言&#xff0c;覆盖了150多个国家和地区。这一庞大的流量池吸引了众多国内电商人尝试在TikTok上进行业务拓展。本文将探讨如果要在美国运营TikTok直播&am…

ruoyi定时任务使用

使用没有什么特别的&#xff0c;不再赘述&#xff0c;可参见前端文档 或下面的文章 ruoyi若依定时任务的基本使用_若依框架定时任务怎么用-CSDN博客 只说一下被调度任务的建立 1、在调用的类上添加Component("后期在调用任务创建用的伪类的名称") 称为伪类是因为…

MySql性能调优03-[SQL优化]

SQL优化 MySQL优化SQL优化-不要写select *SQL优化-小表驱动大表&#xff0c;而不是大表驱动小表SQL优化-连接查询代替子查询SQL优化-提升group by的效率SQL优化-使用limitSQL优化-union all代替unionSQL优化-join的表不宜过多 MySQL优化 trace工具 set session optimizer_trac…

把Docker的虚拟磁盘文件移动到别的盘符

今天清理C盘空间&#xff0c;发现一个很大的文件 ext4.vhdx 足有 15G 之多&#xff0c;发现这个是Docker的虚拟磁盘文件&#xff0c;于是在网上找到移到它的办法&#xff0c;使用 PowerShell 执行下面命令 查看Docker状态和版本 wsl -l -v 关闭Docker服务 wsl --shutdown …

Kithara与OpenCV (一)

Kithara使用 OpenCV 库 目录 Kithara使用 OpenCV 库简介需求和支持的环境构建 OpenCV 库使用 CMake 进行配置以与 Kithara 一起工作 使用 OpenCV 库设置项目运行 OpenCV 代码图像采集和 OpenCV自动并行化限制和局限性1.系统建议2.实时限制3.不支持的功能和缺失的功能4.显示 Ope…

Python 数据类型与基础概念

在 Python 编程中&#xff0c;理解和掌握数据类型和基础概念是至关重要的。这些概念不仅帮助我们更有效地编写代码&#xff0c;还使我们能够创建更加复杂和功能丰富的应用程序。本文将详细介绍 Python 中的基本数据类型及其相关操作&#xff0c;并涵盖一些重要的基础概念。 1.…

数字化打造行业生态产业链,企业新增益全知道

在当今数字化时代&#xff0c;利用数字化打造行业生态产业链成为企业发展的重要战略选择。那么&#xff0c;这一举措究竟能为企业带来哪些新增益呢&#xff1f;让我们一探究竟。 一、运营效率大幅提高 数字化技术就像一条神奇的纽带&#xff0c;将产业链上的各个环节紧紧相…

Python函数 之 匿名函数

1.概念 匿名函数: 使用 lambda 关键字 定义的表达式&#xff0c;称为匿名函数. 2.语法 lambda 参数, 参数: 一行代码 # 只能实现简单的功能&#xff0c;只能写一行代码 # 匿名函数 一般不直接调用&#xff0c;作为函数的参数使用的 3.代码 4.练习 # 1, 定义匿名函数, 参数…

32路串口服务器 应用领域

32路串口服务器在多个领域有着广泛的应用&#xff0c;以下是详细的应用实例&#xff1a; 一、工业自动化 在工业自动化领域&#xff0c;32路串口服务器发挥着举足轻重的作用。传统的工业设备往往采用串口通信方式&#xff0c;而串口服务器能够将这些设备接入网络&#xff0c;…

C++ 开源库

1 PDFium PDFium 是一个开源的 PDF 渲染和处理库&#xff0c;最初由 Foxit Software 开发&#xff0c;并于2014年捐赠给了 Chromium 项目。PDFium 旨在为各种应用程序提供高效、灵活的 PDF 渲染和操作功能。 2 代码地址 https://github.com/chromium/pdfium 主要特性 渲染…

集训 Day 3 总结 虚树 + dfs tree + 基环树

虚树 虚树&#xff0c;顾名思义是 只关注原树上的某些 关键点&#xff0c;在保留原树祖孙关系的前提下建出的一棵边数、点数大大减少的树 适用于优化某些在整棵树上进行 d p dp dp、 d f s dfs dfs 的问题 通常是题目中出现多次询问&#xff0c;每次给出树上的一些关键点&a…

11网络层-分组转发算法

路由 分组转发 1&#xff09;从数据报的首部提取目的主机的IP地址D&#xff0c;得出目的网络地址N 2&#xff09;若N就是与此路由器直接相连的某个网络地址&#xff0c;则进行直接交付&#xff0c;不需要经过其他路由器&#xff0c;直接将数据报交付给目的主机&#xff08;这…

人为因素:为什么网络安全不仅仅关乎技术

关注公众号网络研究观获取更多最新内容。 我们生活在一个生活与技术日益紧密交织的世界。但在构建防火墙和安装防病毒软件时&#xff0c;我们常常会忘记一个关键因素&#xff1a;人的行为。 网络犯罪分子正是利用了人为因素&#xff0c;利用巧妙的心理战术绕过最强大的安全措…

【MySQL基础篇】事务

事务简介 事务是一组操作的集合&#xff0c;它是一个不可分割的工作单位&#xff0c;事务会把所有的操作作为一个整体一起向系统提交或或撤销操作请求&#xff0c;即这些操作要么同时成功&#xff0c;要么同时失败。 典型事例&#xff1a;银行转账操作 假设张三向李四进行转账…

Python:正则表达式相关整理

最近因为一些原因频繁使用正则表达式&#xff0c;因为以前系统整理过关于正则表达式的相关知识&#xff0c;所以这里仅记录使用期间遇到的问题。 本文内容基于re包 1. match和search方法的区别 在Python中&#xff0c;re.search和re.match都是用于匹配字符串的正则表达式函数&a…

防火墙NAT、智能选路综合实验

一、实验拓扑 二、实验要求 1&#xff0c;办公区设备可以通过电信链路和移动链路上网(多对多的NAT&#xff0c;并且需要保留一个公网IP不能用来转换) 2&#xff0c;分公司设备可以通过总公司的移动链路和电信链路访问到Dmz区的http服务器 3&#xff0c;多出口环境基于带宽比例…

Curator分布式锁

Curator 是一个用于 Apache ZooKeeper 的客户端库&#xff0c;提供了更高级的抽象和工具&#xff0c;以简化 ZooKeeper 的使用。Curator 是由 Netflix 开发的&#xff0c;并已成为分布式应用程序中使用 ZooKeeper 的事实标准。它解决了原生 ZooKeeper API 使用复杂、易出错的问…