`
cuijiemin
  • 浏览: 265581 次
  • 性别: Icon_minigender_1
  • 来自: 杭州
社区版块
存档分类
最新评论

理解COM编程中的“种类”(Category)概念

阅读更多

编译/hangwire

下载范例源代码
问题:
我要编写一个支持ActiveX文档插件(Plug-ins)的应用程序。为了创建一个已安装插件的菜单,在程序启动时我扫描注册表查找已安装的ActiveX组件。对于每一个ActiveX组件创建一个实例并查询一个叫IMyAppPlugin的专门接口。如果这个接口存在,那么我就认为这个组件就是我的程序所要的插件。这样做好像行不通,尤其是安装有多个ActiveX组件时做起来就更困难。有没有更好的办法处理这种问题?
解答:
对于这种情况,Windows确实有更好的办法来解决:既种类(category)。对于开发人员来说,种类是一种ActiveX控件。名字可以随意取,如“My Acme Plugin”或者“Blue Insertable Thingies”。对于COM而言,种类只是一个GUID——不同的是种类用CATID表示GUID,这有点像表示某个类的GUID叫做CLSID一样。
那么在实际编程中如何使用CATID呢?首先要生成一个新的GUID(使用GUIDGEN或其它的同类程序),我们且把这个新生成的GUID叫做CATID_AcmePlugin。然后,用一个专门的COM接口ICatRegister来注册你的种类。完成这个工作的地方一般是在DllRegisterServer函数中。为了获得ICatRegister接口,必须调用CoCreateInstance或实现同样功能的函数。
// 在 DllRegisterServer中
CComPtr<icatregister></icatregister> spcr;
spcr.CoCreateInstance(CLSID_StdComponentCategoriesMgr,
    NULL, CLSCTX_INPROC);
这段代码使用ATL智能指针;CComPtr<icatregister></icatregister>::CoCreateInstance还能用ICatRegister的IID调用::CoCreateInstance。一旦有了ICatRegister,便可以调用RegisterCategories。方法是先用自己的种类信息填写CATEGORYINFO结构。
CATEGORYINFO catinfo;
catinfo.catid = CATID_AcmePlugin;
catinfo.lcid = 0x0409;  // locale=english
USES_CONVERSION;        // uses A2W
wcscpy(catinfo.szDescription, 
   A2W("My Acme Plugin."));
pcr->RegisterCategories(1, &catinfo);
接下来的任务是如何告诉COM你的COM类是Acme Plugin。ICatRegister也有相应的方法来做这件事情,它就是RegisterClassImplCategories。
// 也是在DllRegisterServer中
CATID catid = CATID_AcmePlugin;
pcr->RegisterClassImplCategories(
   CLSID_MyPluginObj, 1, &catid);
这样就注册了你的COM类,实现种类CATID_AcmePlugin。是不是很简单啊!这些都是此类编程的套路。ICatRegister将有关哪个类实现哪个种类的信息放入注册表,以便Windows能快速读到它,而不用像你最开始所做的那样去实例化每一个组件来查找IMyAcmePlugin接口。
与种类的注册类似,ICatRegister也有用注销种类的方法,这两个方法对于种类而言都是必须的(相对于实现而言),也就是说,你的COM类需要其容器来实现那些种类。当你的组件需要专门的回调接口时,就必须实现种类。下面是完整的ICatRrgister接口:
//
ICatRegister 
////////////////////////////////////////////////////////////////
// ICatRegister interface, edited from comcat.h
//
class ICatRegister : public IUnknown {
public:
   virtual HRESULT RegisterCategories(
      ULONG cCategories,              // number of categories to register
      CATEGORYINFO rgCategoryInfo[]); // info for each one
        
   virtual HRESULT UnRegisterCategories(
      ULONG cCategories,             // number of categories to unregister
      CATID rgcatid[]);              // their CATIDs

   virtual HRESULT RegisterClassImplCategories(
      REFCLSID rclsid,               // COM class ID
      ULONG cCategories,             // number of categories it implements
      CATID rgcatid[]);              // their CATIDs

   virtual HRESULT UnRegisterClassImplCategories( 
      REFCLSID rclsid,              // COM class ID
      ULONG cCategories,            // num implemented categories to unreg
      CATID rgcatid[]);             // their CATIDs

   virtual HRESULT RegisterClassReqCategories( 
      REFCLSID rclsid,              // COM class ID
      ULONG cCategories,            // number of categories it requires
      CATID rgcatid[]);             // required CATIDs

   virtual HRESULT UnRegisterClassReqCategories( 
      REFCLSID rclsid,              // COM class ID
      ULONG cCategories,            // number of req''''d categories to unreg
      CATID rgcatid[]);             // CATIDs to unregister
};
//
对于注册种类编程的实例请参见VC知识库的另外一篇文章:“编写可复用性更强的MFC代码”。
讲了那么多有关注册的问题。现在假设你写了一个容器并且你想要产生一个插件(Acme Plugins)清单——既实现CATID_AcmePlugin的组件。Windoews提供了另一个接口,ICatInformation:
//
ICatInformation 
class ICatInformation : public IUnknown {
public:
   // Enumerate all categories
   virtual HRESULT EnumCategories( 
      LCID lcid,
      IEnumCATEGORYINFO ** ppenumCategoryInfo);

   // Get locale-specific category descriptor
   virtual HRESULT GetCategoryDesc( 
      REFCATID rcatid,
      LCID lcid,
      LPWSTR *pszDesc);

   // Enumerate classes that implement/require given categories
   virtual HRESULT EnumClassesOfCategories( 
      ULONG cImplemented,
      CATID rgcatidImpl[],
      ULONG cRequired,
      CATID rgcatidReq[],
      IEnumGUID **ppenumClsid);

   // Determine if class implements/requires given categories
   virtual HRESULT IsClassOfCategories( 
      REFCLSID rclsid,
      ULONG cImplemented,
      CATID rgcatidImpl[  ],
      ULONG cRequired,
      CATID rgcatidReq[  ]);

   // Enumerate categories implemented by given class
   virtual HRESULT EnumImplCategoriesOfClass( 
      REFCLSID rclsid,
      IEnumGUID **ppenumCatid);

   // Enumerate categories required by given class
   virtual HRESULT EnumReqCategoriesOfClass( 
      REFCLSID rclsid,
      IEnumGUID **ppenumCatid);
};
//
用这接口可以枚举实现给定种类的类。为了说明ICatInformation接口使用,我写了一个小程序CatView,用这个程序可以浏览系统中注册的种类。如图一所示:
<shapetype id="_x0000_t75" coordsize="21600,21600" o:spt="75" o:preferrelative="t" path="m@4@5l@4@11@9@11@9@5xe" filled="f" stroked="f"><stroke joinstyle="miter"></stroke><formulas><f eqn="if lineDrawn pixelLineWidth 0"></f><f eqn="sum @0 1 0"></f><f eqn="sum 0 0 @1"></f><f eqn="prod @2 1 2"></f><f eqn="prod @3 21600 pixelWidth"></f><f eqn="prod @3 21600 pixelHeight"></f><f eqn="sum @0 0 1"></f><f eqn="prod @6 1 2"></f><f eqn="prod @7 21600 pixelWidth"></f><f eqn="sum @8 21600 0"></f><f eqn="prod @7 21600 pixelHeight"></f><f eqn="sum @10 21600 0"></f></formulas><path o:extrusionok="f" gradientshapeok="t" o:connecttype="rect"></path><lock v:ext="edit" aspectratio="t"></lock></shapetype><shape id="_x0000_i1025" type="#_x0000_t75"><imagedata src="file:///C:/DOCUME~1/ADMINI~1/LOCALS~1/Temp/msoclip1/01/clip_image001.png" o:title="cfig0107_1"></imagedata></shape>
图一 CatView 浏览系统中注册的种类
下面是CatView 有关的代码:(全部源代码可以从本文最前面的链接下载)
// CoolCat.h — helper stuff for COMponent categories.
//
#pragma once
#include <atlbase.h></atlbase.h>

//////////////////
// Helper function to get GUID in human-readable format as CString.
//
inline CString CStringFromGuid(GUID& guid)
{
    LPOLESTR pstr=NULL;
    StringFromCLSID(guid, &pstr);
    return CString(pstr);
}

////////////////
// Handy Category Information class. Instantiate and go.
//
class CCatInformation : public CComPtr<icatinformation></icatinformation> {
public:
    CCatInformation() {
        CoCreateInstance(CLSID_StdComponentCategoriesMgr, NULL, 
                         CLSCTX_INPROC);
        ASSERT(p);
    }
};

//////////////////
// Handy class to enumerate categories
//
class CCatIterator {
protected:
    CComPtr<ienumcategoryinfo></ienumcategoryinfo> spEnumCatInfo; // IEnumCATEGORYINFO 
    CCatInformation spCatInfo;                // ICatInformation
public:
    CCatIterator(LCID lcid = GetUserDefaultLCID()) {
        HRESULT hr = spCatInfo->EnumCategories(lcid, &spEnumCatInfo);
        ASSERT(SUCCEEDED(hr));
    }
    BOOL Next(CATEGORYINFO& catinfo) {
        ULONG nRet=0;
        return SUCCEEDED(spEnumCatInfo->Next(1, &catinfo, &nRet)) && 
                         nRet==1;
    }
};

//////////////////
// Handy class to enumerate classes that implement a category
//
class CCatClassIterator {
protected:
    CComPtr<ienumclsid></ienumclsid> spEnumCLSID; // IEnumCLSID
    CCatInformation spCatInfo;       // ICatInformation
public:
    CCatClassIterator(CATID* arImplCatids, ULONG nImpl,
        CATID* arReqdCatids=NULL, ULONG nReqd=0) {

        HRESULT hr = spCatInfo->EnumClassesOfCategories(
            nImpl,           // num implemented cats in array
            arImplCatids,    // array of cats to look for (implement)
            nReqd,           // num required categories in array
            arReqdCatids,    // array of required categories to look for
            &spEnumCLSID);   // IEnum returned
        ASSERT(SUCCEEDED(hr));
    }
    BOOL Next(CLSID& clsid) {
        ULONG nRet=0;
        return SUCCEEDED(spEnumCLSID->Next(1, &clsid, &nRet)) && nRet==1;
    }
};

View.h 
#pragma once
//////////////////
// Right pane is a list of controls that implement a category.
//
class CRightView : public CListView {
public:
    CRightView();
    virtual ~CRightView();
    BOOL ShowCategory(CATID& catid);

protected:
    virtual void OnInitialUpdate(); // called first time after construct
    virtual void OnDraw(CDC* pDC);  // overridden to draw this view
    virtual BOOL PreCreateWindow(CREATESTRUCT& cs);
    DECLARE_DYNCREATE(CRightView)
    DECLARE_MESSAGE_MAP()
};

//////////////////
// Left pane is a list of categories.
//
class CLeftView : public CListView {
public:
    virtual ~CLeftView();
    virtual void OnDraw(CDC* pDC);  // overridden to draw this view
    virtual BOOL PreCreateWindow(CREATESTRUCT& cs);
    void SetRightPane(CRightView* pRightPane) {
        m_pRightPane = pRightPane;
    }

protected:
    CRightView* m_pRightPane;
    CLeftView();

    void PopulateCategoryList();

    virtual void OnInitialUpdate(); // called first time after construct
    afx_msg void OnItemChanged(NMHDR* pNMHDR, LRESULT* pRes);
    afx_msg LRESULT OnWinMgr(WPARAM wp, LPARAM lp);

    DECLARE_MESSAGE_MAP()
    DECLARE_DYNCREATE(CLeftView)
};

LeftView.cpp 
//
#include "stdafx.h"
#include "View.h"
#include "WinMgr.h"
#include "CoolCat.h"

#ifdef _DEBUG
#define new DEBUG_NEW
#undef THIS_FILE
static char THIS_FILE[] = __FILE__;
#endif

IMPLEMENT_DYNCREATE(CLeftView, CListView)
BEGIN_MESSAGE_MAP(CLeftView, CListView)
    ON_NOTIFY_REFLECT(LVN_ITEMCHANGED,OnItemChanged)
    ON_REGISTERED_MESSAGE(WM_WINMGR, OnWinMgr)
END_MESSAGE_MAP()

CLeftView::CLeftView() : m_pRightPane(NULL)
{
}

CLeftView::~CLeftView()
{
}

BOOL CLeftView::PreCreateWindow(CREATESTRUCT& cs)
{
    cs.style |= LVS_REPORT | LVS_SORTASCENDING | LVS_NOSORTHEADER;
    return CListView::PreCreateWindow(cs);
}

void CLeftView::OnDraw(CDC* pDC)
{
}

//////////////////
// First-time init: add column headers
//
void CLeftView::OnInitialUpdate()
{
    CListView::OnInitialUpdate();
    
    const COLWIDTH = 250;
    CListCtrl& lc = GetListCtrl();
    lc.InsertColumn(0, _T("Category Name"),LVCFMT_LEFT,COLWIDTH);
    lc.InsertColumn(1, _T("CATID"),LVCFMT_LEFT,COLWIDTH,1);

    PopulateCategoryList();
}

//////////////////
// Populate list of categories.
//
void CLeftView::PopulateCategoryList()
{
    CListCtrl& lc = GetListCtrl();
    lc.DeleteAllItems();

    CATEGORYINFO catinfo;
    CCatIterator it;
    while (it.Next(catinfo)) {
        // add category name to list 
        CString sName = catinfo.szDescription;
        if (sName.IsEmpty()) {
            sName = _T("<unkown></unkown>");
        }
        int iItem = lc.InsertItem(0,sName);

        // Add CATID as 1st subitem
        lc.SetItemText(iItem,1,CStringFromGuid(catinfo.catid));
    }
}

//////////////////
// User selected a new category: show controls in right pane.
//
void CLeftView::OnItemChanged(NMHDR* pNMHDR, LRESULT* pRes)
{
    NMLISTVIEW nm = *(NMLISTVIEW*)pNMHDR;
    if (nm.iItem>=0 && (nm.uNewState & LVIS_SELECTED)) {
        CListCtrl& lc = GetListCtrl();
        CString sguid = lc.GetItemText(nm.iItem,1);
        CATID catid;
        USES_CONVERSION;
        if (SUCCEEDED(CLSIDFromString(T2OLE((LPCTSTR)sguid),&catid)))
            m_pRightPane->ShowCategory(catid);
        else
            MessageBeep(0);
    }
    *pRes= 0;
}

//////////////////
// Handle WinMgr request for size info: compute TOFIT size for list view,
// which is sum of widths of columns.
//
LRESULT CLeftView::OnWinMgr(WPARAM wp, LPARAM lp)
{
    ASSERT(lp);
    NMWINMGR& nmw = *(NMWINMGR*)lp;
    if (nmw.code==NMWINMGR::GET_SIZEINFO && (int)wp==GetDlgCtrlID()) {
        CSize sz(0,0);
        CListCtrl& lc = GetListCtrl();
        int nCols = lc.GetHeaderCtrl()->GetItemCount();
        for (int iCol=0; iCol");
        lc.SetItemText(iItem,1,sProgID);
    }
    return TRUE;
}
//
CatView是个典型的将窗口切分成两个窗格的程序,左边窗格是种类清单,当单击其中一条记录,右边窗格会显示相应的实现这个种类的类信息。(CatView程序中使用了一个类CwinMgr,这个类将在另外一篇文章中做专门讨论:“创建一个随心所欲定制窗口尺寸的类”)。图一所示,选中“Active Scripting Engine with Parsing”列表项,则右边的窗格将显示实现它的各个组件:XML,Java,Visual Basic和PerlScript脚本引擎。CatView中的两个主要的函数是CLeftView::PopulateCategoryList 和 CRightView::ShowCategory。为了简单起见,我实现了一些有用的辅助类(在头文件CoolCat.h中)。第一各类是CCatInformation,它用ATL智能指针封装了ICatInformation接口。
//
class CCatInformation : public CComPtr<icatinformation></icatinformation> {
public:
   CCatInformation() {
      CoCreateInstance(CLSID_StdComponentCategoriesMgr, 
         NULL, CLSCTX_INPROC);
   }
};
有了CCatInformation类,就不用再调用CoCreateInstance——实例化,然后直接使用类对象。
CCatInformation spCatInfo;
spCatInfo->SomeMethod(...);
为了枚举系统中的组件种类,调用ICatInformation::EnumCategories 。这个函数回传一个IEnumCATEGORYINFO 接口指针,然后用这个指针枚举种类。
// IEnumCATEGORYINFO
CCatInformation spCatInfo;
CComPtr<ienumcategoryinfo></ienumcategoryinfo> spEnumCatInfo;
HRESULT hr = spCatInfo->EnumCategories(
   GetUserDefaultLCID(),&spEnumCatInfo);
ASSERT(SUCCEEDED(hr));
  
// 使用指针枚举种类
ULONG nRet=0;
CATEGORYINFO catinfo;
   while (SUCCEEDED(spEnumCatInfo->Next(1, 
   &catinfo, &nRet)) && nRet==1) {
   // add catinfo to list 
}
COM的技术机制实际上就这么几招。即使是使用ATL智能指针也是如此,我把这几招COM编程技术都封装在一个辅助类CCatIterator中,以便使用起来方便一些。有了CcatIterator辅助类,要做的事情很简单:
CATEGORYINFO catinfo;
CCatIterator it;
while (it.Next(catinfo)) {
  // add catinfo to list
}
CLeftView::PopulateCategoryList用CCatIterator类以名字和每个种类的CATID构造列表视图。每次调用Next来将下一个种类的信息填入catinfo。在这里请记住我的一些经验之谈,在进行COM编程时,做好是编写一些自己的小型辅助类以免去处理那些头疼的HRESULTs和接口指针,尖括弧以及Release操作。我是个唯美主义者,要求自己的代码不仅要正确运行,还要求好看。
一旦具备了CATID,就可以用ICatInformation来得到实现种类的COM类清单。例如,实现CATID_AcmePlugin的所有控件。其中最关键的部分是ICatInformation::EnumClassesOfCategories以及枚举器IEnumCLSID。同样我也写了一个类来封装这些东西。
CLSID clsid;
CCatClassIterator it(&catid, 1);
while (it.Next(clsid)) {
  // add clsid to list
}
与ICatInformation::EnumClassesOfCategories类似,CCatClassIterator可以使你指定多个实现的种类。如“查找所有AcmePlugin和Blue Insertable Thingies 控件”。在这种情况下,要传递一个包含两个CATIDs的数组。你还能指定一个或多个必须的种类来查找需要一个或多个给定的控件。通过缺省值NULL,CCatClassIterator隐藏了所有额外的参数。
以上内容讨论了COM技术中对种类的编程。下面将谈谈CatView的其余部分,它与Windows及其MFC有关。CatView是一个文档/视结构的应用,但CDummyDoc只是为MFC而存在的。CMainFrame::OnCreateClient创建由窗格并在执行了通常的CframeWnd之后与左边窗格关联起来。在程序中唯有CLeftView::OnWinMgr是比较特殊的东西,它通过添加列宽来报告列表视图画面的TOFIT尺寸。(有关WinMgr和TOFIT的内容,请参见另外一篇文章:“创建一个随心所欲定制窗口尺寸的类”)。
本文附带的CatView例子可以从文章开始处的链接下载。编译后可以在自己的机器上运行,以观察机器上注册的种类。你会注意到一些晦涩难董的种类(如Visual InterDev Web Site Wizards)以及一些通用的控件,自动化对象和可插入种类。从COM的历史看,可插入种类是整个种类概念的祖先。回溯到早期,Visual Basic需要某种方式来获得哪个对象能被插入表单(forms),不用实例化每一个在注册表中的类来查找(QueryInterface)IOleInPlaceObject接口。解决方法是添加一个专门的键值,HKCR\CLSID\{CLSID}\Insertable,它告诉Visual Basic 类是可插入的(insertable)。后来微软扩展了这个机制变成更一般的概念,它就是我们在这里所说的种类。今天,Insertable键是个遗留下来的东西,对于要在16位应用插入32位对象,Insertable键是必不可少的。
分享到:
评论

相关推荐

    《理解COM编程中的“种类”(Category)概念》配套VC源代码

    我要编写一个支持ActiveX文档插件(Plug-ins)的应用程序。为了创建一个已安装插件的菜单,在程序启动时我扫描注册表查找已安装的ActiveX组件。对于每一个ActiveX组件创建一...关键字:COM,Category,种类,组件对象模型

    CategoryTest Demo代码

    在这个名为"CategoryTest Demo代码"的压缩包中,我们主要关注的是Objective-C中的一个关键特性——Category(分类)。Category允许我们在不修改原有类源代码的情况下,为其添加新的方法,极大地增强了代码的可复用性...

    禅与 Objective-C 编程艺术

    书中可能涵盖了类、对象、继承、消息传递等基本OOP概念,以及协议、分类和Category等Objective-C特有的特性。 消息传递是Objective-C的核心,不同于其他语言的函数调用,Objective-C通过发送消息来调用方法,这赋予...

    028050-Category Theory-Steve Awodey, 2006_2.pdf

    在函数式编程语言(比如Haskell)的设计中,范畴论的概念被直接应用于编程语言的构造中,使得这些语言的表达能力得到增强。 范畴论教科书,比如Steve Awodey编写的《范畴论》(Category Theory),不仅为数学专业的...

    Objective-C面向对象编程的基本应用

    通过分析和学习提供的ObjDemo压缩包中的源码,我们可以更深入地理解Objective-C的面向对象编程实践。例如,查看类的定义、对象的创建和交互,以及协议的使用等。源码中的例子可以帮助我们更好地掌握面向对象编程的...

    禅与 Objective-C 编程艺术 .zip

    4. **分类(Category)**:Objective-C允许通过类别向已有的类添加方法,这是一种强大的代码组织和扩展手段,可以在不修改原有代码的情况下扩展类的行为。 5. ** Blocks**:Objective-C的Blocks是闭包的一种实现,...

    objective-c编程 第2版=objective-c program 2nd edition_13889311

    此外,可能会介绍分类(Category)和协议(Protocol),这是Objective-C实现代码复用和接口规范的重要机制。 3. **消息传递**:Objective-C中的对象通过消息传递进行通信。书中会讲解消息传递的原理,包括动态绑定...

    Object-C编程之道

    《Object-C编程之道》这本书是面向那些已经具备C语言基础,并希望深入理解Objective-C这门编程语言的读者。Objective-C,简称ObjC,是苹果公司开发的C语言的超集,主要用于iOS和macOS平台的应用程序开发。它在C语言...

    Effective Objective-C 2.0&Obj;-C高级编程

    - Objective-C编程中,设计模式如单例、工厂、代理、观察者等是常见实践,理解并灵活运用这些模式可以提高代码的可读性和可维护性。 9. **Objective-C 2.0新特性**: - 包括快速枚举、属性(properties)的改进、...

    oc宝典oc宝典oc宝典

    1. **Objective-C基础**:学习OC的基础语法,包括类、对象、消息传递、协议、分类、继承等核心概念。理解OC如何通过消息传递实现方法调用,以及如何利用动态性进行灵活编程。 2. **内存管理**:了解ARC(Automatic ...

    php递归实现无限级分类库

    在PHP编程中,无限级分类是一项常见的需求,特别是在构建如电商、博客等系统时,我们需要对商品、文章等数据进行...通过对这个库的实践和理解,你不仅可以解决实际项目中的分类问题,还能进一步提高自己的编程技巧。

    php实现无限级分类

    在PHP编程中,无限级分类是一种常见的需求,特别是在构建网站导航菜单、文章分类或组织具有层级关系的数据时。无限级分类允许我们创建任意深度的树状结构,而无需预先确定最大的层级数量。以下是对这个话题的详细...

    Excel2003高级VBA编程宝典.doc

    - 通过系统学习,逐渐理解VBA编程的基本概念。 - 能够看懂并写出自己的程序,提升了解决问题的能力。 **2. 实践应用** - 掌握了如何创建有效的自定义函数。 - 学会了调试技巧及错误处理方法。 - 提高了处理...

    SSD5练习与答案

    Category.cpp和Categories.cpp这两个文件名暗示了可能涉及类和分类的概念。在C++编程中,"Category"通常表示一个类别或类型,而"Categories"可能是多个类别的集合。这可能涉及到面向对象编程(OOP)中的类设计,包括...

    php无限极分类源码.

    在PHP编程中,无限极分类是一种常见的数据组织方式,尤其在处理树形结构的数据时,如网站导航菜单、论坛板块、商品分类等。无限级分类允许我们构建深度未知的层级结构,使得数据管理更加灵活。下面,我们将深入探讨...

    项目中经常用到的一些分类及工具类___下载.zip

    首先,让我们理解什么是分类(Category)。在Objective-C中,分类是一种扩展已有类功能的方法,而无需继承。它允许我们向已有的类添加方法,而不会创建新的类实例。在iOS开发中,开发者常利用分类来扩展Foundation或...

    Topoi:逻辑的分类分析Topoi: The Categorial Analysis of Logic

    3. **子对象分类器(Subobject Classifier)**:子对象分类器是拓扑中的一个重要概念,它允许我们以一种结构化的方式讨论真值和逻辑连接词。在传统逻辑中,真值通常只有两个:“真”和“假”。但在拓扑逻辑中,子...

    php无限分类的示例

    在PHP编程中,无限分类是一种常见的需求,尤其在处理如文章分类、商品分类等具有层级关系的数据时。无限分类能够使我们构建出任意深度的树形...理解这些概念和方法,将有助于你在实际开发中灵活应对无限分类的需求。

    无限极分类

    无限极分类在编程领域,尤其是数据管理中是一个重要的概念,特别是在构建层次结构或者树状结构的数据模型时。这种分类方式允许我们创建一个无限制深度的分类系统,例如网站的导航菜单、论坛的板块结构或者组织架构等...

Global site tag (gtag.js) - Google Analytics