热门标签 | HotTags
当前位置:  开发笔记 > 编程语言 > 正文

CListCtrl的Report风格自绘[转]

转自http:jingyan.baidu.comarticle5bbb5a1b38af1113eaa17910.html非常棒感谢作者CListCtrl是MFC中运用最广泛的控件之

转自http://jingyan.baidu.com/article/5bbb5a1b38af1113eaa17910.html  非常棒 感谢作者



CListCtrl是MFC中运用最广泛的控件之一,很多软件都有CListCtrl的身影,但是对于CListCtrl的自绘,很多朋友都犯了难,网上虽然有很多人讲解怎么自绘,但是实现出的效果都不是太友好,本篇讲解CListCtrl的Report自绘,对于LVS_ICON风格,我们采用窗口模拟进行绘制。先说一下Report重绘的注意事项。

CListCtrl的Report风格自绘
CListCtrl的Report风格自绘


方法/步骤

  1. 1

    CListCtrl控件分为上下两部分,上面的标头位置为标头控件,下方是我们需要绘制的CListCtrl,标头控件我们需要继承CHeaderCtrl进行标头控件的重绘,然后在CListCtrl中我们子类化CHeaderCtrl控件

  2. 2

    CHeaderCtrl控件高度设置,有两种方法

    1:通过SetFont强制撑大控件的高度

    //创建字体

    CFont Font;

    Font.CreatePointFont(m_uItemHeight,TEXT("宋体"));

    //设置字体

    SetFont(&Font);

    2:通过HDM_LAYOUT消息修改高度,添加ON_MESSAGE(HDM_LAYOUT, OnLayout)

    LRESULT CSkinHeaderCtrl::OnLayout( WPARAM wParam, LPARAM lParam )

    {

         LRESULT lResult = CHeaderCtrl::DefWindowProc(HDM_LAYOUT, 0, lParam); 

         HD_LAYOUT &hdl = *( HD_LAYOUT * ) lParam; 

         RECT *prc = hdl.prc; 

         WINDOWPOS *pwpos = hdl.pwpos; 

         int nHeight = 28;

         pwpos->cy = nHeight; 

         prc->top = nHeight; 

         return lResult; 

    }

    上述代码中设置了控件的高度为28

  3. 3

    重绘CListCtrl,先将Owner Draw Fixed设为true或者Create的时候添加LVS_OWNERDRAWFIXED属性,目的就是让控件能响应DrawItem从而实现自绘,此处需要注意,对于LVS_ICON风格,DrawItem不会被系统调用,不管是否添加LVS_OWNERDRAWFIXED属性,当然可以通过在WM_PAINT内进行绘制,不过这样会给Report带来诸多的麻烦,所以,我们通过模拟来实现LVS_ICON的绘制

  4. 4

    CListCtrl高度的设置,因为CListCtrl并没有诸如SetItemHeight之类的函数进行节点高度的设置,所以我们必须自己实现这样的功能

    void CSkinListCtrl::SetItemHeight( int nHeight )

    {

          m_nHeightItem = nHeight;

          CRect rcWin;

          GetWindowRect(&rcWin);

          WINDOWPOS wp;

          wp.hwnd = m_hWnd;

          wp.cx = rcWin.Width();

          wp.cy = rcWin.Height();

          wp.flags = SWP_NOACTIVATE | SWP_NOMOVE | SWP_NOOWNERZORDER | SWP_NOZORDER;

          SendMessage(WM_WINDOWPOSCHANGED, 0, (LPARAM)&wp);

    }

    同时添加ON_WM_MEASUREITEM_REFLECT消息反射

    void CSkinListCtrl::MeasureItem( LPMEASUREITEMSTRUCT lpMeasureItemStruct )

    {

          if (m_nHeightItem>0)

          {

                lpMeasureItemStruct->itemHeight = m_nHeightItem;

          }

    }

    这里提到了消息发射,注意区分和消息映射的区别,关于消息反射的概念不做赘述,可以通过MSDN进行查阅

  5. 5

    CListCtrl为我们提供了SetExtendedStyle函数可以添加CListCtrl的很多扩展属性,这里强调一点LVS_EX_CHECKBOXES会导致ON_WM_MEASUREITEM_REFLECT消息反射不被接收,LVS_EX_GRIDLINES会造成节点矩形不准确,所以,我们需要重载屏蔽这些属性

    DWORD CSkinListCtrl::SetExtendedStyle( DWORD dwNewStyle )

    {

          if ( dwNewStyle & LVS_EX_CHECKBOXES )

          {

                dwNewStyle &=~LVS_EX_CHECKBOXES;

                dwNewStyle &=~LVS_EX_GRIDLINES;

          }

          return __super::SetExtendedStyle(dwNewStyle);

    }

  6. 6

    Check设置,因为我们上面屏蔽了LVS_EX_CHECKBOXES,所以默认情况下,当我们插入节点的时候,默认是被Check的,所以, 还需要重载四个InsertItem函数在里面默认将Check取消掉,例如

    int CSkinListCtrl::InsertItem( const LVITEM* pItem )

    {

          int nResult = __super::InsertItem(pItem);

          SetCheck(pItem->iItem,FALSE);

          return nResult;

    }

  7. 7

    关于标头控件节点的拖动,当我们将鼠标放在两个节点的交叉处,鼠标会变成拖动的样式,此时按住左键可以拉伸节点的宽度,但是很多时候我们不想让前面的几个进行拖动,这个时候,我们需要在CHeaderCtrl派生类中重载OnChildNotify函数

    BOOL CSkinHeaderCtrl::OnChildNotify(UINT uMessage, WPARAM wParam, LPARAM lParam, LRESULT * pLResult)

    {

          //变量定义

          NMHEADER * pNMHearder=(NMHEADER*)lParam;

          //拖动消息

          if ((pNMHearder->hdr.code==HDN_BEGINTRACKA)||(pNMHearder->hdr.code==HDN_BEGINTRACKW))

          {

                //禁止拖动

                if (pNMHearder->iItem<(INT)m_uLockCount)

                {

                      *pLResult&#61;TRUE;

                      return TRUE;

                }

          }

          return __super::OnChildNotify(uMessage,wParam,lParam,pLResult);

    }

    其中m_uLockCount就是禁止拖动的个数&#xff0c;比如设置为2&#xff0c;则前面2个节点将不能拉伸宽度&#xff0c;我们可以在写一个方法

    //设置锁定

    VOID CSkinHeaderCtrl::SetLockCount(UINT uLockCount)

    {

          //设置变量

          m_uLockCount&#61;uLockCount;

          return;

    }

    END


绘制的步骤

  1. 1

    创建CHeaderCtrl的派生类CSkinHeaderCtrl&#xff0c;现在主要看一下OnPaint的绘制

    //重画函数

    VOID CSkinHeaderCtrl::OnPaint() 

    {

          CPaintDC dc(this);

          //获取位置

          CRect rcRect;

          GetClientRect(&rcRect);

         //双缓冲&#xff0c;内存DC  

          CMemoryDC BufferDC(&dc,rcRect);

          //设置 DC

          BufferDC.SetBkMode(TRANSPARENT);

          BufferDC.SetTextColor(m_colNormalText);

          BufferDC.SelectObject(GetCtrlFont());

          //绘画背景

          if (m_pBackImg !&#61; NULL && !m_pBackImg->IsNull())

                m_pBackImg->Draw(&BufferDC,rcRect);

          if (m_pPressImg !&#61; NULL && !m_pPressImg->IsNull() && m_bPress)

          {

                CRect rcItem;

                GetItemRect(m_uActiveItem,&rcItem);

                m_pPressImg->Draw(&BufferDC,rcItem);

          }

          //绘画子项

          CRect rcItem;

          HDITEM HDItem;

          TCHAR szBuffer[64];

          for (INT i&#61;0;i

          {

                //构造变量

                HDItem.mask&#61;HDI_TEXT;

                HDItem.pszText&#61;szBuffer;

                HDItem.cchTextMax&#61;CountArray(szBuffer);

                //获取信息

                GetItem(i,&HDItem);

                GetItemRect(i,&rcItem);

                if (m_pGridImg !&#61; NULL && !m_pGridImg->IsNull())

                      m_pGridImg->DrawImage(&BufferDC,(rcItem.right-m_pGridImg->GetWidth()),(rcItem.Height()-m_pGridImg->GetHeight())/2);

                //绘画标题

                rcItem.DeflateRect(3,1,3,1);

                   BufferDC.DrawText(szBuffer,lstrlen(szBuffer),&rcItem,DT_END_ELLIPSIS|DT_SINGLELINE|DT_VCENTER|DT_CENTER);

          }

          return;

    }

  2. 2

    鼠标按下

    void CSkinHeaderCtrl::OnLButtonDown( UINT nFlags, CPoint point )

    {

          CRect rcItem;

          for (INT i&#61;0;i

          {

                GetItemRect(i,&rcItem);

                if ( PtInRect(&rcItem,point) )

                {

                      m_bPress &#61; true;

                      m_uActiveItem &#61; i;

                      break;

                }

          }

          RedrawWindow(NULL,NULL,RDW_FRAME|RDW_INVALIDATE|RDW_ERASE|RDW_ERASENOW);

          __super::OnLButtonDown(nFlags, point);

    }

  3. 3

    鼠标抬起

    void CSkinHeaderCtrl::OnLButtonUp( UINT nFlags, CPoint point )

    {

          m_bPress &#61; false;

          RedrawWindow(NULL,NULL,RDW_FRAME|RDW_INVALIDATE|RDW_ERASE|RDW_ERASENOW);

          __super::OnLButtonUp(nFlags, point);

    }

  4. 4

    新建CListCtrl的派生类CSkinListCtrl

    struct  tagItemImage 

    {

          CImageEx *pImage;

          int nItem;

    };

    typedef vector CItemImgArray;

    重点看一下绘制

  5. 5

    //绘画函数

    VOID CSkinListCtrl::DrawItem(LPDRAWITEMSTRUCT lpDrawItemStruct)

    {

          //变量定义

          CRect rcItem&#61;lpDrawItemStruct->rcItem;

          CDC * pDC&#61;CDC::FromHandle(lpDrawItemStruct->hDC);

          CMemoryDC BufferDC(pDC,rcItem);

          //获取属性

          INT nItemID&#61;lpDrawItemStruct->itemID;

          INT nColumnCount&#61;m_SkinHeaderCtrl.GetItemCount();

          //绘画区域

          CRect rcClipBox;

          BufferDC.GetClipBox(&rcClipBox);

          //设置环境

          BufferDC.SetBkMode(TRANSPARENT);

          BufferDC.SetTextColor(m_colNormalText);

          BufferDC.SelectObject(GetCtrlFont());

          BufferDC->FillSolidRect(&rcItem,m_colBack);

          //绘画焦点

          if (lpDrawItemStruct->itemState&ODS_SELECTED)

          {

                if (m_pSelectImg !&#61; NULL && !m_pSelectImg->IsNull())

                      m_pSelectImg->Draw(&BufferDC,rcItem);

          }

          else if ( m_uActiveItem &#61;&#61; nItemID )

          {

                if (m_pHovenImg !&#61; NULL && !m_pHovenImg->IsNull())

                      m_pHovenImg->Draw(&BufferDC,rcItem);

          }

          //绘画子项

          for (INT i&#61;0;i

          {

                //获取位置

                CRect rcSubItem;

                GetSubItemRect(nItemID,i,LVIR_BOUNDS,rcSubItem);

                //绘画判断

                if (rcSubItem.left>rcClipBox.right) break;

                if (rcSubItem.right

                //绘画数据

                DrawReportItem(&BufferDC,nItemID,rcSubItem,i);

          }

          return;

    }

  6. 6

    //绘画数据

    VOID CSkinListCtrl::DrawReportItem(CDC * pDC, INT nItem, CRect & rcSubItem, INT nColumnIndex)

    {

          //获取文字

          TCHAR szString[256]&#61;TEXT("");

          GetItemText(nItem,nColumnIndex,szString,CountArray(szString));

          //绘画文字

          rcSubItem.left&#43;&#61;5;

          //绘制CheckButton

          if( nColumnIndex &#61;&#61; 0 )

          {

                if ((m_pCheckImg !&#61; NULL && !m_pCheckImg->IsNull()) && (m_pUnCheckImg !&#61; NULL && !m_pUnCheckImg->IsNull()))

                {

                      if( GetCheck(nItem) )

                            m_pCheckImg->DrawImage(pDC,rcSubItem.left&#43;2,rcSubItem.top&#43;(rcSubItem.Height()-m_pCheckImg->GetHeight())/2);

                      else

                            m_pUnCheckImg->DrawImage(pDC,rcSubItem.left&#43;2,rcSubItem.top&#43;(rcSubItem.Height()-m_pUnCheckImg->GetHeight())/2);

                      rcSubItem.left&#43;&#61;(8&#43;m_pCheckImg->GetWidth());

                }

                CItemImgArray::iterator iter &#61; m_ItemImgArray.begin();

                for (;iter !&#61; m_ItemImgArray.end(); &#43;&#43;iter )

                {

                      if ( iter->nItem &#61;&#61; nItem )

                      {

                            CImageEx *pImage &#61; iter->pImage;

                            if (pImage !&#61; NULL && !pImage->IsNull())

                            {

                                  pImage->DrawImage(pDC,rcSubItem.left&#43;2,rcSubItem.top&#43;(rcSubItem.Height()-pImage->GetHeight())/2);

                                  rcSubItem.left&#43;&#61;(8&#43;pImage->GetWidth());

                            }

                            break;

                      }

                }

          }

          pDC->DrawText(szString,lstrlen(szString),&rcSubItem,DT_VCENTER|DT_SINGLELINE|DT_END_ELLIPSIS);

          return;

    }

  7. 7

    这里说一下节点的图标&#xff0c;因为每个节点的图标都可能不一样&#xff0c;而且&#xff0c;节点的图标随时可能因为其他的需要发生更换&#xff0c;所以这里我们采用vector进行图标的管理&#xff0c;

    BOOL CSkinListCtrl::InsertImage( int nItem,LPCTSTR lpszFileName )

    {

          CItemImgArray::iterator iter &#61; m_ItemImgArray.begin();

          for (;iter !&#61; m_ItemImgArray.end(); &#43;&#43;iter )

          {

                if ( iter->nItem &#61;&#61; nItem )

                {

                      //当该节点存在图片时&#xff0c;我们更新新的图片资源

                      if( iter->pImage !&#61; NULL )

                      {

                            RenderEngine->RemoveImage(iter->pImage);

                            iter->pImage &#61; RenderEngine->GetImage(lpszFileName);

                            return TRUE;

                      }

                }

          }

          tagItemImage ItemImage;

          //设置数据

          ItemImage.nItem &#61; nItem;

          ItemImage.pImage &#61; RenderEngine->GetImage(lpszFileName);

          if (NULL &#61;&#61; ItemImage.pImage)

                return FALSE;

          else

          {

                m_ItemImgArray.push_back(ItemImage);

                return TRUE;

          }

    }

  8. 8

    void CSkinListCtrl::OnMouseMove( UINT nFlags, CPoint point )

    {

          CRect rcItem;

          static UINT uOldActiveItem &#61; -1;

          for (int i&#61;0;i

          {

                GetItemRect(i,rcItem,LVIR_BOUNDS);

                if ( PtInRect(&rcItem,point) )

                {

                      m_uActiveItem &#61; i;

                      if( uOldActiveItem !&#61; m_uActiveItem )

                      {

                            uOldActiveItem &#61; m_uActiveItem;

                            Invalidate(FALSE);

                      }

                      break;

                }

          }

          __super::OnMouseMove(nFlags, point);

    }

    这里注意&#xff0c;我们设置了一个静态变量来保存之前鼠标移动过的节点&#xff0c;此变量的意义很简单&#xff0c;因为无法保证用户是不是在同一个节点晃动鼠标&#xff0c;如果只要监听到鼠标移动消息就进行刷新&#xff0c;这样的话会消耗过多的cpu资源

  9. 9

    void CSkinListCtrl::OnLButtonDown( UINT nFlags, CPoint point )

    {

          if (m_pCheckImg !&#61; NULL && !m_pCheckImg->IsNull())

          {

                CRect rcSubItem,rcIcon;

                for (int i&#61;0;i

                {

                      GetItemRect(i,rcSubItem,LVIR_BOUNDS);

                      rcIcon.left &#61; rcSubItem.left&#43;7;

                      rcIcon.top &#61; rcSubItem.top&#43;(rcSubItem.Height()-m_pCheckImg->GetHeight())/2;

                      rcIcon.right &#61; rcIcon.left &#43; m_pCheckImg->GetWidth();

                      rcIcon.bottom &#61; rcIcon.top &#43; m_pCheckImg->GetHeight();

                      if ( PtInRect(&rcIcon,point) )

                      {

                            SetCheck(i,!GetCheck(i));

                            SetItemState(i, LVIS_FOCUSED | LVIS_SELECTED,LVIS_FOCUSED | LVIS_SELECTED);

                            SetSelectionMark(i);

                            Invalidate(FALSE);

                            break;

                      }

                }

          }

          __super::OnLButtonDown(nFlags, point);

    }

  10. 10

    关于Report的自绘就完成了&#xff0c;看一下调用过程

    END


调用过程

  1. m_ListCtrl5.m_SkinHeaderCtrl.SetBackImage(TEXT("Res\\ListCtrl\\folder_nav_item_bg_hover.png"),&CRect(2,2,2,2));

    m_ListCtrl5.m_SkinHeaderCtrl.SetPressImage(TEXT("Res\\ListCtrl\\folder_nav_item_bg_pressed.png"),&CRect(2,2,2,2));

    m_ListCtrl5.m_SkinHeaderCtrl.SetGridImage(TEXT("Res\\ListCtrl\\category_sep.png"));

    m_ListCtrl5.SetHovenImage(TEXT("Res\\ListCtrl\\item_bg_hover.png"),&CRect(2,2,2,2));

    m_ListCtrl5.SetSelectImage(TEXT("Res\\ListCtrl\\item_bg_selected.png"),&CRect(2,2,2,2));

    m_ListCtrl5.SetCheckImage(TEXT("Res\\ListCtrl\\check.png"),TEXT("Res\\ListCtrl\\uncheck.png"));

    m_ListCtrl5.SetScrollImage(&m_ListCtrl5,TEXT("Res\\Scroll\\SKIN_SCROLL.bmp"));

    m_ListCtrl5.InsertColumn( 0, TEXT("文件名"), LVCFMT_LEFT, 150 );

    m_ListCtrl5.InsertColumn( 1, TEXT("大小"), LVCFMT_LEFT, 100 );

    m_ListCtrl5.InsertColumn( 2, TEXT("修改时间"), LVCFMT_LEFT, 150 );

    for (int i&#61;0;i<8;i&#43;&#43;)

    {

          m_ListCtrl5.InsertItem(i,TEXT("跟我学MFC.zip"));

          m_ListCtrl5.SetItemText(i,1,TEXT("126MB"));

          m_ListCtrl5.SetItemText(i,2,TEXT("2013-10-17 18:13"));

    }

    m_ListCtrl5.InsertImage(0,TEXT("Res\\ListCtrl\\DocType.png"));

    m_ListCtrl5.InsertImage(1,TEXT("Res\\ListCtrl\\FolderType.png"));

    m_ListCtrl5.InsertImage(2,TEXT("Res\\ListCtrl\\ImgType.png"));

    m_ListCtrl5.InsertImage(3,TEXT("Res\\ListCtrl\\PdfType.png"));

    m_ListCtrl5.InsertImage(4,TEXT("Res\\ListCtrl\\PptType.png"));

    m_ListCtrl5.InsertImage(5,TEXT("Res\\ListCtrl\\RarType.png"));

    //m_ListCtrl5.InsertImage(6,TEXT("Res\\ListCtrl\\VsdType.png"));

    m_ListCtrl5.InsertImage(7,TEXT("Res\\ListCtrl\\VideoType.png"));

    //更新资源图片

    m_ListCtrl5.InsertImage(0,TEXT("Res\\ListCtrl\\VideoType.png"));

    m_ListCtrl5.SetItemHeight(30);

    //标头控件禁止拖动

    //m_ListCtrl6.m_SkinHeaderCtrl.EnableWindow(FALSE);

    //让第一个节点禁止拖动

    m_ListCtrl6.m_SkinHeaderCtrl.SetLockCount(1);






推荐阅读
  • android listview OnItemClickListener失效原因
    最近在做listview时发现OnItemClickListener失效的问题,经过查找发现是因为button的原因。不仅listitem中存在button会影响OnItemClickListener事件的失效,还会导致单击后listview每个item的背景改变,使得item中的所有有关焦点的事件都失效。本文给出了一个范例来说明这种情况,并提供了解决方法。 ... [详细]
  • ASP.NET2.0数据教程之十四:使用FormView的模板
    本文介绍了在ASP.NET 2.0中使用FormView控件来实现自定义的显示外观,与GridView和DetailsView不同,FormView使用模板来呈现,可以实现不规则的外观呈现。同时还介绍了TemplateField的用法和FormView与DetailsView的区别。 ... [详细]
  • MyBatis多表查询与动态SQL使用
    本文介绍了MyBatis多表查询与动态SQL的使用方法,包括一对一查询和一对多查询。同时还介绍了动态SQL的使用,包括if标签、trim标签、where标签、set标签和foreach标签的用法。文章还提供了相关的配置信息和示例代码。 ... [详细]
  • 欢乐的票圈重构之旅——RecyclerView的头尾布局增加
    项目重构的Git地址:https:github.comrazerdpFriendCircletreemain-dev项目同步更新的文集:http:www.jianshu.comno ... [详细]
  • 本文介绍了在MFC下利用C++和MFC的特性动态创建窗口的方法,包括继承现有的MFC类并加以改造、插入工具栏和状态栏对象的声明等。同时还提到了窗口销毁的处理方法。本文详细介绍了实现方法并给出了相关注意事项。 ... [详细]
  • 如何自行分析定位SAP BSP错误
    The“BSPtag”Imentionedintheblogtitlemeansforexamplethetagchtmlb:configCelleratorbelowwhichi ... [详细]
  • GetWindowLong函数
    今天在看一个代码里头写了GetWindowLong(hwnd,0),我当时就有点费解,靠,上网搜索函数原型说明,死活找不到第 ... [详细]
  • 向QTextEdit拖放文件的方法及实现步骤
    本文介绍了在使用QTextEdit时如何实现拖放文件的功能,包括相关的方法和实现步骤。通过重写dragEnterEvent和dropEvent函数,并结合QMimeData和QUrl等类,可以轻松实现向QTextEdit拖放文件的功能。详细的代码实现和说明可以参考本文提供的示例代码。 ... [详细]
  • 本文分享了一个关于在C#中使用异步代码的问题,作者在控制台中运行时代码正常工作,但在Windows窗体中却无法正常工作。作者尝试搜索局域网上的主机,但在窗体中计数器没有减少。文章提供了相关的代码和解决思路。 ... [详细]
  • CSS3选择器的使用方法详解,提高Web开发效率和精准度
    本文详细介绍了CSS3新增的选择器方法,包括属性选择器的使用。通过CSS3选择器,可以提高Web开发的效率和精准度,使得查找元素更加方便和快捷。同时,本文还对属性选择器的各种用法进行了详细解释,并给出了相应的代码示例。通过学习本文,读者可以更好地掌握CSS3选择器的使用方法,提升自己的Web开发能力。 ... [详细]
  • 本文讨论了一个关于cuowu类的问题,作者在使用cuowu类时遇到了错误提示和使用AdjustmentListener的问题。文章提供了16个解决方案,并给出了两个可能导致错误的原因。 ... [详细]
  • 本文介绍了一个在线急等问题解决方法,即如何统计数据库中某个字段下的所有数据,并将结果显示在文本框里。作者提到了自己是一个菜鸟,希望能够得到帮助。作者使用的是ACCESS数据库,并且给出了一个例子,希望得到的结果是560。作者还提到自己已经尝试了使用"select sum(字段2) from 表名"的语句,得到的结果是650,但不知道如何得到560。希望能够得到解决方案。 ... [详细]
  • 本文详细介绍了Spring的JdbcTemplate的使用方法,包括执行存储过程、存储函数的call()方法,执行任何SQL语句的execute()方法,单个更新和批量更新的update()和batchUpdate()方法,以及单查和列表查询的query()和queryForXXX()方法。提供了经过测试的API供使用。 ... [详细]
  • Java学习笔记之使用反射+泛型构建通用DAO
    本文介绍了使用反射和泛型构建通用DAO的方法,通过减少代码冗余度来提高开发效率。通过示例说明了如何使用反射和泛型来实现对不同表的相同操作,从而避免重复编写相似的代码。该方法可以在Java学习中起到较大的帮助作用。 ... [详细]
  • 本文介绍了解决Facebook脸书面试题中插入区间的方法,通过模拟遍历的方式判断当前元素与要插入元素的关系,找到插入点并将新区间插入。同时对算法的时间复杂度和空间复杂度进行了分析。 ... [详细]
author-avatar
手机用户2602930391
这个家伙很懒,什么也没留下!
PHP1.CN | 中国最专业的PHP中文社区 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved | 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有