C++ 内存池 -- C++ Memory Pool

2023-10-31

本文翻译版本出自

http://blog.csdn.net/060/archive/2006/10/08/1326025.aspx

原文作者: DanDanger2000

原文链接: http://www.codeproject.com/cpp/MemoryPool.asp

C++ 内存池

l  下载示例工程 – 105Kb

l  下载源代码 – 17.3Kb

目录
引言
它怎样工作
示例
使用这些代码
好处
关于代码
ToDo
历史
 
引言
C/C++ 的内存分配 ( 通过 malloc new) 可能需要花费很多时。
更糟糕的是,随着时间的流逝,内存 (memory) 将形成碎片,所以一个应用程序的运行会越来越慢当它运行了很长时间和 / 或执行了很多的内存分配 ( 释放 ) 操作的时候。特别是,你经常申请很小的一块内存,堆 (heap) 会变成碎片的。
解决方案:你自己的内存池
一个 ( 可能的 ) 解决方法是内存池 (Memory Pool)
在启动的时候,一个 内存池 ”(Memory Pool) 分配一块很大的内存,并将会将这个大块 (block) 分成较小的块 (smaller chunks) 。每次你从内存池申请内存空间时,它会从先前已经分配的块 (chunks) 中得到, 而不是从操作系统。最大的优势在于:
非常少 ( 几没有 堆碎片
比通常的内存申请 / 释放 ( 比如通过 malloc new ) 的方式快
另外,你可以得到以下好处:
检查任何一个指针是否在内存池里
写一个 堆转储 ( Heap-Dump )” 到你的硬盘 ( 对事后的调试非常有用 )
某种 内存泄漏检测 ( memory-leak detection )” :当你没有释放所有以前分配的内存时,内存池 (Memory Pool) 会抛出一个断言 ( assertion ).
它怎样工作
让我们看一看内存池 (Memory Pool) UML 模式图:
这个模式图只显示了类 CMemoryPool 的一小部分,参看由 Doxygen生成的文档以得到详细的类描述。
 
一个关于内存块(MemoryChunks)的单词
你应该从模式图中看到, 内存池 (Memory Pool) 管理了一个指向结构体 SMemoryChunk  ( m_ptrFirstChunk m_ptrLastChunk , and  m_ptrCursorChunk ) 的指针。这些块 (chunks) 建立一个内存块 (memory chunks) 的链表。各自指向链表中的下一个块 (chunk) 。当从操作系统分配到一块内存时,它将完全的被 SMemoryChunk s 管理。让我们近一点看看一个块 (chunk)

typedef struct  SMemoryChunk
{
  TByte 
*Data ;             // The actual Data

  std::size_t DataSize ;    // Size of the "Data"-Block
  std::size_t UsedSize ;    // actual used Size
  bool IsAllocationChunk ;  // true, when this MemoryChunks
                            
//
 Points to a "Data"-Block
                            
// which can be deallocated via "free()"

  SMemoryChunk *Next ;      // Pointer to the Next MemoryChunk
                            
// in the List (may be NULL)


}
 SmemoryChunk;
每个块(chunk)持有一个指针,指针指向:
一小块内存 ( Data )
从块 (chunk) 开始的可用内存的总大小 ( DataSize )
实际使用的大小 ( UsedSize )
以及一个指向链表中下一个块 (chunk) 的指针。
第一步:预申请内存(pre-allocating the memory)
当你调用 CmemoryPool 的构造函数,内存池 (Memory Pool) 将从操作系统申请它的第一块 ( 大的 ) 内存块 (memory-chunk)
/*Constructor
*****************
*/

CMemoryPool::CMemoryPool(
const  std::size_t  & sInitialMemoryPoolSize,
                         
const  std::size_t  & sMemoryChunkSize,
                         
const  std::size_t  & sMinimalMemorySizeToAllocate,
                         
bool  bSetMemoryData)
{
  m_ptrFirstChunk  
= NULL ;
  m_ptrLastChunk   
= NULL ;
  m_ptrCursorChunk 
= NULL ;

  m_sTotalMemoryPoolSize 
= 0 ;
  m_sUsedMemoryPoolSize  
= 0 ;
  m_sFreeMemoryPoolSize  
= 0 ;

  m_sMemoryChunkSize   
= sMemoryChunkSize ;
  m_uiMemoryChunkCount 
= 0 ;
  m_uiObjectCount      
= 0 ;

  m_bSetMemoryData               
= bSetMemoryData ;
  m_sMinimalMemorySizeToAllocate 
= sMinimalMemorySizeToAllocate ;

  
// Allocate the Initial amount of Memory from the Operating-System...
  AllocateMemory(sInitialMemoryPoolSize) ;
}

类的所有成员通用的初始化在此完成, AllocateMemory 最终完成了从操作系统申请内存。
/******************
AllocateMemory
*****************
*/

bool  CMemoryPool::AllocateMemory( const  std::size_t  & sMemorySize)
{
  std::size_t sBestMemBlockSize 
= CalculateBestMemoryBlockSize(sMemorySize) ;
  
// allocate from Operating System
  TByte *ptrNewMemBlock = (TByte *) malloc (sBestMemBlockSize) ;
  ...
那么,是如何管理数据的呢?
第二步:已分配内存的分割(segmentation of allocated memory)
正如前面提到的, 内存池( Memory Pool ) 使用 SMemoryChunk s 管理所有数据。从OS申请完内存之后,我们的块(chunks)和实际的内存块(block)之间就不存在联系:
Memory Pool after initial allocation
我们需要分配一个结构体 SmemoryChunk 的数组来管理内存块:
   //  (AllocateMemory()continued) : 
  ...
  unsigned 
int  uiNeededChunks  =  CalculateNeededChunks(sMemorySize) ;
  
//  allocate Chunk-Array to Manage the Memory
  SMemoryChunk  * ptrNewChunks  =  
    (SMemoryChunk 
* ) malloc ((uiNeededChunks  *   sizeof (SMemoryChunk))) ;
  assert(((ptrNewMemBlock) 
&&  (ptrNewChunks)) 
                           
&&   " Error : System ran out of Memory " ) ;
  ...
CalculateNeededChunks() 负责计算为管理已经得到的内存需要的块(chunks)的数量。分配完块(chunks)之后(通过 malloc ) ptrNewChunks 将指向一个 SmemoryChunk s 的数组。注意,数组里的块 (chunks) 现在持有的是垃圾数据,因为我们还没有给 chunk-members 赋有用的数据。内存池的堆 (Memory Pool-"Heap"):
Memory Pool after  SMemoryChunk  allocation
还是那句话,数据块 (data block) chunks 之间没有联系。但是, AllocateMemory() 会照顾它。 LinkChunksToData() 最后将把数据块 (data block) chunks 联系起来,并将为每个 chunk-member 赋一个可用的值。
//  (AllocateMemory()continued) : 
  ...
  
//  Associate the allocated Memory-Block with the Linked-List of MemoryChunks
   return  LinkChunksToData(ptrNewChunks, uiNeededChunks, ptrNewMemBlock) ;
让我们看看 LinkChunksToData()
/******************
LinkChunksToData
*****************
*/

bool  CMemoryPool::LinkChunksToData(SMemoryChunk  * ptrNewChunks, 
     unsigned 
int  uiChunkCount, TByte  * ptrNewMemBlock)
{
  SMemoryChunk 
*ptrNewChunk = NULL ;
  unsigned 
int uiMemOffSet = 0 ;
  
bool bAllocationChunkAssigned = false ;
  
for(unsigned int i = 0; i < uiChunkCount; i++)
  
{
    
if(!m_ptrFirstChunk)
    
{
      m_ptrFirstChunk 
= SetChunkDefaults(&(ptrNewChunks[0])) ;
      m_ptrLastChunk 
= m_ptrFirstChunk ;
      m_ptrCursorChunk 
= m_ptrFirstChunk ;
    }

    
else
    
{
      ptrNewChunk 
= SetChunkDefaults(&(ptrNewChunks[i])) ;
      m_ptrLastChunk
->Next = ptrNewChunk ;
      m_ptrLastChunk 
= ptrNewChunk ;
    }

    
    uiMemOffSet 
= (i * ((unsigned int) m_sMemoryChunkSize)) ;
    m_ptrLastChunk
->Data = &(ptrNewMemBlock[uiMemOffSet]) ;

    
// The first Chunk assigned to the new Memory-Block will be 
    
// a "AllocationChunk". This means, this Chunks stores the
    
// "original" Pointer to the MemBlock and is responsible for
    
// "free()"ing the Memory later....
    if(!bAllocationChunkAssigned)
    
{
      m_ptrLastChunk
->IsAllocationChunk = true ;
      bAllocationChunkAssigned 
= true ;
    }

  }

  
return RecalcChunkMemorySize(m_ptrFirstChunk, m_uiMemoryChunkCount) ;
}

让我们一步步地仔细看看这个重要的函数:第一行检查链表里是否已经有可用的块(chunks):
  ...
  
if ( ! m_ptrFirstChunk)
  ...
我们第一次给类的成员赋值:
  ...
  m_ptrFirstChunk 
=  SetChunkDefaults( & (ptrNewChunks[ 0 ])) ;
  m_ptrLastChunk 
=  m_ptrFirstChunk ;
  m_ptrCursorChunk 
=  m_ptrFirstChunk ;
  ...
m_ptrFirstChunk 现在指向块数组( chunks-array ) 第一个块,每一个块严格的管理来自内存( memory block ) m_sMemoryChunkSize 个字节。一个 偏移量 ”(offset) ——这个值是可以计算的所以每个 (chunk) 能够指向内存块 ( memory block) 的特定部分。
 
  uiMemOffSet  =  (i  *  ((unsigned  int ) m_sMemoryChunkSize)) ;
  m_ptrLastChunk
-> Data  =   & (ptrNewMemBlock[uiMemOffSet]) ; 
另外,每个新的来自数组的 SmemoryChunk 将被追加到链表的最后一个元素(并且它自己将成为最后一个元素):
  ...
  m_ptrLastChunk
-> Next  =  ptrNewChunk ;
  m_ptrLastChunk 
=  ptrNewChunk ;
  ...
在接下来的 " for loop中,内存池(memory pool)将连续的给数组中的所有块(chunks)赋一个可用的数据。
Memory and chunks linked together, pointing to valid data
最后,我们必须重新计算每个块(chunk)能够管理的总的内存大小。这是一个费时的,但是在新的内存追加到内存池时必须做的一件事。这个总的大小将被赋值给chunk的 DataSize  成员。
/******************
RecalcChunkMemorySize
*****************
*/

bool  CMemoryPool::RecalcChunkMemorySize(SMemoryChunk  * ptrChunk, 
                  unsigned 
int  uiChunkCount)
{
  unsigned 
int uiMemOffSet = 0 ;
  
for(unsigned int i = 0; i < uiChunkCount; i++)
  
{
    
if(ptrChunk)
    
{
      uiMemOffSet 
= (i * ((unsigned int) m_sMemoryChunkSize)) ;
      ptrChunk
->DataSize = 
        (((unsigned 
int) m_sTotalMemoryPoolSize) - uiMemOffSet) ;
      ptrChunk 
= ptrChunk->Next ;
    }

    
else
    
{
     assert(
false && "Error : ptrChunk == NULL") ;
     
return false ;
    }

  }

  
return true ;
}

RecalcChunkMemorySize 之后,每个chunk都知道它指向的空闲内存的大小。所以,将很容易确定一个chunk是否能够持有一块特定大小的内存:当 DataSize 成员大于 ( 或等于 ) 已经申请的内存大小以及 DataSize 成员是 0 ,于是 chunk 有能力持有一块内存。最后,内存分割完成了。为了不让事情太抽象,我们假定内存池 (memory pool ) 包含600字节,每个chunk持有100字节。
  
Memory segmentation finished. Each chunk manages exactly 100 bytes
第三步:从内存池申请内存(requesting memory from the memory pool)
那么,如果用户从内存池申请内存会发生什么?最初,内存池里的所有数据是空闲的可用的:
 
All memory blocks are available
我们看看 GetMemory :
/******************
GetMemory
*****************
*/

void   * CMemoryPool::GetMemory( const  std::size_t  & sMemorySize)
{
  std::size_t sBestMemBlockSize 
= CalculateBestMemoryBlockSize(sMemorySize) ;  
  SMemoryChunk 
*ptrChunk = NULL ;
  
while(!ptrChunk)
  
{
    
// Is a Chunks available to hold the requested amount of Memory ?
    ptrChunk = FindChunkSuitableToHoldMemory(sBestMemBlockSize) ;
    
if (!ptrChunk)
    
{
      
// No chunk can be found
      
// => Memory-Pool is to small. We have to request 
      
//    more Memory from the Operating-System....
      sBestMemBlockSize = MaxValue(sBestMemBlockSize, 
        CalculateBestMemoryBlockSize(m_sMinimalMemorySizeToAllocate)) ;
      AllocateMemory(sBestMemBlockSize) ;
    }

  }


  
// Finally, a suitable Chunk was found.
  
// Adjust the Values of the internal "TotalSize"/"UsedSize" Members and 
  
// the Values of the MemoryChunk itself.
  m_sUsedMemoryPoolSize += sBestMemBlockSize ;
  m_sFreeMemoryPoolSize 
-= sBestMemBlockSize ;
  m_uiObjectCount
++ ;
  SetMemoryChunkValues(ptrChunk, sBestMemBlockSize) ;

  
// eventually, return the Pointer to the User
  return ((void *) ptrChunk->Data) ;
}

当用户从内存池中申请内存是,它将从链表搜索一个能够持有被申请大小的chunk。那意味着:
那个chunk的 DataSize 必须大于或等于被申请的内存的大小;  
那个chunk的 UsedSize  必须是 0
 
这由  FindChunkSuitableToHoldMemory    方法完成。如果它返回 NULL ,那么在内存池中没有可用的内存。这将导致 AllocateMemory  的调用 ( 上面讨论过 ) ,它将从 OS 申请更多的内存。如果返回值不是 NULL 一个可用的 chunk 被发现。 SetMemoryChunkValues 会调整 chunk 成员的值,并且最后 Data 指针被返回给用户 ...
/******************
    SetMemoryChunkValues
    *****************
*/

void  CMemoryPool::SetMemoryChunkValues(SMemoryChunk  * ptrChunk, 
     
const  std::size_t  & sMemBlockSize)
{
  
if(ptrChunk) 
  
{
    ptrChunk
->UsedSize = sMemBlockSize ;
  }

  ...
    }
 
示例
假设,用户从内存池申请 250 字节:
 
 
Memory in use
如我们所见,每个内存块(chunk)管理100字节,所以在这里250字节不是很合适。发生了什么事?Well, GetMemory   从第一个chunk返回  Data 指针并把它的 UsedSize 设为300字节,因为300字节是能够被管理的内存的最小值并大于等于250。那些剩下的 (300 - 250 = 50) 字节被称为内存池的 "memory overhead" 。这没有看起来的那么坏,因为这些内存还可以使用 ( 它仍然在内存池里 )
FindChunkSuitableToHoldMemory 搜索可用 chunk 时,它仅仅从一个空的 chunk 跳到另一个空的 chunk 。那意味着,如果某个人申请另一块内存 (memory-chunk) ,第四块 ( 持有 300 字节的那个 ) 会成为下一个可用的 ("valid") chunk
 
Jump to next valid chunk
使用代码
使用这些代码是简单的、直截了当的:只需要在你的应用里包含 "CMemoryPool.h" ,并添加几个相关的文件到你的 IDE/Makefile:
  • CMemoryPool.h
  • CMemoryPool.cpp
  • IMemoryBlock.h
  • SMemoryChunk.h
你只要创建一个 CmemoryPool 类的实例,你就可以从它里面申请内存。所有的内存池的配置在 CmemoryPool 类的构造函数 ( 使用可选的参数 ) 里完成。看一看头文件 ("CMemoryPool.h") Doxygen-doku 。所有的文件都有详细的 (Doxygen-) 文档。
应用举例
MemPool::CMemoryPool  * g_ptrMemPool  =   new  MemPool::CMemoryPool() ;
char   * ptrCharArray  =  ( char   * ) g_ptrMemPool -> GetMemory( 100 ) ;
...
g_ptrMemPool
-> FreeMemory(ptrCharArray,  100 ) ;
delete g_ptrMemPool ;
好处
内存转储(Memory dump)
你可以在任何时候通过 WriteMemoryDumpToFile(strFileName) 写一个 "memory dump" 到你的 HDD 。看看一个简单的测试类的构造函数 ( 使用内存池重载了 new delete 运算符 )
 
/******************
Constructor
*****************
*/

MyTestClass::MyTestClass()
{
   m_cMyArray[
0= 'H' ;
   m_cMyArray[
1= 'e' ;
   m_cMyArray[
2= 'l' ;
   m_cMyArray[
3= 'l' ;
   m_cMyArray[
4= 'o' ;
   m_cMyArray[
5= NULL ;
   m_strMyString 
= "This is a small Test-String" ;
   m_iMyInt 
= 12345 ;

   m_fFloatValue 
= 23456.7890f ;
   m_fDoubleValue 
= 6789.012345 ;

   Next 
= this ;
}

MyTestClass  * ptrTestClass  =   new  MyTestClass ; 
g_ptrMemPool
-> WriteMemoryDumpToFile( " MemoryDump.bin " ) ;
看一看内存转储文件 ("MemoryDump.bin"):
如你所见,在内存转储里有 MyTestClass 类的所有成员的值。明显的, "Hello" 字符串 ( m_cMyArray ) 在那里,以及整型数 m_iMyInt  (3930 0000 = 0x3039 = 12345 decimal) 等等。这对调式很有用。
速度测试
我在 Windows 平台上做了几个非常简单的测试 ( 通过 timeGetTime() ) ,但是结果说明内存池大大提高了应用程序的速度。所有的测试在 Microsoft Visual Studio .NET 2003 debug 模式下 ( 测试计算机 Intel Pentium IV Processor (32 bit), 1GB RAM, MS Windows XP Professional).
// Array-test (Memory Pool): 
for (unsigned  int  j  =   0 ; j  <  TestCount; j ++ )
{
        
// ArraySize = 1000
    char *ptrArray = (char *) g_ptrMemPool->GetMemory(ArraySize)  ;
    g_ptrMemPool
->FreeMemory(ptrArray, ArraySize) ;
}

  
    
// Array-test (Heap):
for (unsigned  int  j  =   0 ; j  <  TestCount; j ++ )
{
        
// ArraySize = 1000
    char *ptrArray = (char *) malloc(ArraySize)  ;
    free(ptrArray) ;
   }

Results for the "array-test
 
    //Class-Test for MemoryPool and Heap (overloaded new/delete)
  // Class-Test for MemoryPool and Heap (overloaded new/delete) 
for (unsigned  int  j  =   0 ; j  <  TestCount; j ++ )
{
    MyTestClass 
*ptrTestClass = new MyTestClass ;
    delete ptrTestClass ;
}

 
Results for the "classes-test" (overloaded new/delete operators)
关于代码
这些代码在Windows和Linux平台的下列编译器测试通过:
  • Microsoft Visual C++ 6.0
  • Microsoft Visual C++ .NET 2003
  • MinGW (GCC) 3.4.4 (Windows)
  • GCC 4.0.X (Debian GNU Linux)
Microsoft Visual C++ 6.0(*.dsw*.dsp) Microsoft Visual C++ .NET 2003 (*.sln*.vcproj) 的工程文件已经包含在下载中。内存池仅用于 ANSI/ISO C++, 所以它应当在任何 OS 上的标准的 C++ 编译器编译。在 64 位处理器上应当没有问题。
注意 :内存池不是线程安全的。
这个内存池还有许多改进的地方 ;-) ToDo 列表包括:
对于大量的内存, memory-"overhead" 能够足够大。
某些 CalculateNeededChunks 调用能够通过从新设计某些方法而去掉
更多的稳定性测试 ( 特别是对于那些长期运行的应用程序 )
做到线程安全。
 
历史
05.09.2006: Initial release
 
EoF
DanDanger2000



FROM:  http://blog.chinaunix.net/uid-479984-id-2114952.html

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

C++ 内存池 -- C++ Memory Pool 的相关文章

  • Python 统计一个纯英文文件中的单词总个数

    import sys import string if len sys argv 1 or sys argv 1 in h help print usage countWord py filename 1 filename 2 finena
  • C++实现链式栈

    pragma once template
  • python可视化石头剪刀布界面

    python是一个简单高效的编程语言 其易于扩展和丰富的库深受人们的欢迎 它让编程变得更简单 易懂 现在它也可以用来写一个一个简单的小游戏 无需网络能玩一天 代码如下 import random import tkinter from tk
  • abb 机械手臂 示例程序

    教学设备的示例程序 如有侵权 立删 MODULE SX815Q1 CONST robtarget pHomeQ1 396 50 0 00 630 00 0 499991 0 500009 0 499998 0 500002 1 1 3 0
  • 算法实践1_线性回归

    参数解释 sklearn linear model LinearRegression fit intercept True normalize False copy X True n jobs None 超参 解释 类型 默认值 fit i
  • 生产API版本及SIMNOW环境说明

  • Java 读取某文件下的所有文件名称以及大小,并输出在xls表格里

    方法一 package com test demo test import lombok Data import java io import java util ArrayList import java util List Title
  • np.clip的使用方法

    np clip的使用方法 参数数量及其作用 示例 参数数量及其作用 np clip是一个截取函数 用于截取数组中小于或者大于某值的部分 并使得被截取部分等于固定值 函数如下 np clip a a min a max out None 该函
  • WSL2中使用GPU

    在WSL2上安装CUDA和NVIDIA HPC SDK 1 WSL2和Ubuntu的安装 2 安装显卡驱动 3 在WSL2中安装CUDA 4 安装 NVIDIA HPC SDK Windows10内部预览版20145及之后的版本的WSL2支
  • 软件设计模式详解 #CSDN博文精选# #IT技术# #软件模式# #设计模式#

    大家好 小C将继续与你们见面 带来精选的CSDN博文 又到周一啦 上周的系统化学习专栏已经结束 我们总共一起学习了20篇文章 这周将开启全新专栏 放假不停学 全栈工程师养成记 在这里 你将收获 将系统化学习理论运用于实践 系统学习IT技术
  • 五、IDEA中创建Web项目

    文章目录 5 1 创建Web项目 5 1 1 创建项目 5 1 2 编写Servlet类 5 2 手动部署项目 5 3 自动部署项目 5 3 1 IDEA集成Tomcat 5 3 2 IDEA部署JavaWeb项目 5 4 war包部署 5
  • css被点击后改变样式,Js 通过点击改变css样式

    通过js 点击按钮去改变目标原始的背景颜色Change html function test4 event if event value 11 取div1 var div1 document getElementById div1 div1
  • voronoi图编程构造_可视化编程真的有那么糟糕?

    作者 Anton Livaja 译者 弯月 责编 屠敏 以下为译文 我想告诉你 如果使用恰当 可视化编程和是图解推理是一个非常强大的工具集 也就是说 只有当可视化编程扎根于数学和计算机科学并建立坚实的基础 才能发挥良好的作用 为了降低编程的
  • 《职场情绪稳定:内在的力量与策略》

    近期发生的新闻热点 如大规模裁员 创业公司倒闭 公共卫生事件等 让公众更加关注稳定情绪和心理健康的问题 在职场中 我们常常遇到各种挑战和压力 如何保持稳定的情绪成了一个重要的话题 首先 让我们分享一些工作中可能引发我们情绪波动的事情 我曾经
  • IT项目管理七

    Tony Prince 和他的团队正在做一个娱乐和健康方面的项目 他们被要求修改现有的成本估计 以便能有一个可靠的评价项目绩效的基线 你的进度和成本目标是在6个月内在200 000美元的预算下完成项目 1 作业一 准备和打印一页类似于图7
  • 求n个数的最小公倍数(C语言)

    Problem Description 求n个数的最小公倍数 Input 输入包含多个测试实例 每个测试实例的开始是一个正整数n 然后是n个正整数 Output 为每组测试数据输出它们的最小公倍数 每个测试实例的输出占一行 你可以假设最后的

随机推荐

  • java项目 畅购商城 购物车

    第10章 购物车 学习目标 能够通过SpringSecurity进行权限控制 掌握购物车流程 掌握购物车渲染 微服务之间的认证访问 1 SpringSecurity权限控制 用户每次访问微服务的时候 先去oauth2 0服务登录 登录后再访
  • 网易游戏(互娱)游戏研发一面&二面(已收到offer)

    简单来讲下上周面网易互娱的心得 因为我不是走内推而是直接怼笔试的 所以上周才有了笔试结果然后被告知面试 我面的岗位是游戏研发工程师 初级 一面 40分钟左右 开始是简单的自我介绍 C 关于C 问的比较简单 因为我跟面试官说我主要学的是Jav
  • 风格回调函数 vs c++风格虚基类

    http www cnblogs com raymon archive 2012 08 28 2660876 html 风格回调函数 vs c 风格虚基类 关于接口定义和调用的对比 c 中也很常用回调函数 比如MFC中 既可以用回调函数的方
  • APP移动端自动化基础及appium环境搭建

    目录 APP移动端自动化测试基础 主流移动端自动化工具 Appium介绍 Appium工作原理 Appium环境搭建 安装前准备工具 安装Android SDK 配置环境变量 安装Python client 安装夜神模拟器 mumu模拟器
  • 一文一图搞懂OSI七层模型

    什么是OSI 所谓的OSI 是由国际化标准组织 ISO 针对开放式网路架构所制定的电脑互连标准 全名是开放式通讯系统互连参考模型 Open System Interconnection Reference Model 简称OSI模型 该模型
  • Air780E

    目录 Air780E编译指南 准备工作 下载源码 注意 需要两个库 准备工具 工具链下载 开始编译 常见编译问题 Air780E编译指南 https wiki luatos com develop compile Air780E html
  • 全面深入彻底理解Python切片操作【原创】

    全面深入彻底理解Python切片操作 原创 我们基本上都知道Python的序列对象都是可以用索引号来引用的元素的 索引号可以是正数由0开始从左向右 也可以是负数由 1开始从右向左 在Python中对于具有序列结构的数据来说都可以使用切片操作
  • 系统权限-数据权限案例分析

    文章目录 前言 一 数据权限 三 源代码下载 四 数据库权限设计图 五 数据权限前台界面 六 数据权限服务端 6 1 aop 拦截 数据范围 6 2 数据实现层ServiceImpl 埋点 七 总结 7 1设计思路 7 2 缺陷 前言 传统
  • TestNG单元测试框架-常用注解介绍以及testng和Junit的区别【杭州多测师_王sir】【杭州多测师】...

    一 TestNG单元测试框架 常用注解介绍 testng学习网址 https www jc2182 com testng testng environment html 1 Before类别和After类别注解按照如下循序执行 Before
  • Java实体类中封装其他实体类并引用

    在Java开发过程中有很多情况是二个表及以上的联合操作 这是需要分清楚表的主次关系 在引用的时候有人会把二个表的数据全都封装在一个实体类中 然后在对这个实体类进行操作 但如果是三个表呢 四个表呢 还都封装在一个实体类吗 这样被封装的实体类的
  • C++ #ifndef、#define、#endif作用

    在C 项目中 ifndef define endif非常常见 接下来就来简单说一下它们的作用 作用 防止头文件被重复引用 防止被重复编译 简介 ifndef 它是if not define的简写 是宏定义的一种 确切的说是预处理功能 宏定义
  • 邮件附件乱码小技巧

    经常有人收到一些Internet邮件 里面有一个附件 例如文件名叫 我的WORD文档 doc 可是用WORD打开后 提示错误或者乱码 遇到这种情况可以用以下步骤解决 1 重命名 把 我的WORD文档 doc 改名字为 我的WORD文档 uu
  • 基于BCM53262交换芯片平台的Linux操作系统移植(三)之配置文件修改

    2018 05 09 10 49 zhoulinhua 2018 05 10 一 单板类型支持 1 修改at91sam9x5ek defconfig定制软件匹配当前单板 buildroot at91 configs at91sam9x5ek
  • windows往磁盘拷文件,拒绝访问

    1 首先确保该盘的权限 全部设置成完全 2 设置完后 如果还会遇到问题 客户端没有所需的特权 方法 cmd以管理权限打开 输入 icacls c setintegritylevel M
  • 视锥裁剪

    背景 视锥体 frustum 是指场景中摄像机的可见的一个锥体范围 它有上 下 左 右 近 远 共6个面组成 在视锥体内的景物可见 反之则不可见 为提高性能 只对其中与视锥体有交集的对象进行绘制 我们计算出视锥体六个面的空间平面方程 将点坐
  • C++:构造、析构、引用与拷贝构造

    构造函数 类的构造函数是类的一种特殊的成员函数 它会在每次创建类的新对象时执行 类的数据成员多为私有的 要对它们进初始化 必须用一个公有函数来进行 同时这个函数应该在且仅在定义对象时自动执行一次 这个函数就是构造函数 它由系统自动调动 用户
  • 一个sql很多个not like的简化语句 (not like 多个值的简化语句)(原创)

    我 SELECT FROM table WHERE zongbu NOT REGEXP 北京 大连 鞍山 天津 香港 沈阳 我 SELECT FROM cpu bidding where bid project name not REGEX
  • Java面试不通过?RabbitMQ你熟悉了吗?

    1 rabbitmq 的使用场景有哪些 跨系统的异步通信 所有需要异步交互的地方都可以使用消息队列 就像我们除了打电话 同步 以外 还需要发短信 发电子邮件 异步 的通讯方式 多个应用之间的耦合 由于消息是平台无关和语言无关的 而且语义上也
  • [sitemap 索引情况提示] 根据 sitemap 的规则[0],当前页面 [pages/index/index] 将被索引

    sitemap 索引情况提示 根据 sitemap 的规则 0 当前页面 pages index index 将被索引 一 报错信息 总结 一 报错信息 代码如下 示例 sitemap 索引情况提示 根据 sitemap 的规则 0 当前页
  • C++ 内存池 -- C++ Memory Pool

    本文翻译版本出自 http blog csdn net 060 archive 2006 10 08 1326025 aspx 原文作者 DanDanger2000 原文链接 http www codeproject com cpp Mem