重写string.h中的字符串操作函数--memmove、memcpy

本文介绍了memmove和memcpy两个函数的功能及使用场景。memmove用于处理可能存在的内存重叠情况,而memcpy则假设内存区域不重叠。当内存区域可能重叠时,memmove能保证正确复制,memcpy则可能导致未定义行为。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

函数原型:void *memmove(void *dest, const void *source, size_t count)

返回值说明:返回指向dest的void *指针

参数说明:dest,source分别为目标串和源串的首地址。count为要移动的字符的个数

函数说明:memmove用于从source拷贝count个字符到dest,如果目标区域和源区域有重叠的话,memmove能够保证源串在被覆盖之前将重叠区域的字节拷贝到目标区域中。

函数原型:void *memcpy(void *dest, const void *source, size_t count);

返回值说明:返回指向dest的void *指针

函数说明:memcpy功能和memmove相同,但是memcpy中dest和source中的区域不能重叠,否则会出现未知结果。

memcpy和memmove 的区别:
  函数memcpy()   从source  指向的区域向dest指向的区域复制count个字符,如果两数组重叠,不定义该函数的行为。  
  而memmove(),如果两函数重叠,赋值仍正确进行。

  memcpy函数假设要复制的内存区域不存在重叠,如果你能确保你进行复制操作的的内存区域没有任何重叠,可以直接用memcpy;  
  如果你不能保证是否有重叠,为了确保复制的正确性,你必须用memmove。

 memcpy的效率会比memmove高一些,如果还不明白的话可以看一些两者的实现:

void *memmove(void *dest, const void *source, size_t count)
{
 assert((NULL 
!= dest) && (NULL != source));
 
char *tmp_source, *tmp_dest;
 tmp_source 
= (char *)source;
 tmp_dest 
= (char *)dest;
 
if((dest + count<source) || (source + count) <dest))
 {
// 如果没有重叠区域
   while(count--)
     
*tmp_dest++ = *tmp_source++;
}
else
{ //如果有重叠
 tmp_source 
+= count - 1;
 tmp_dest 
+= count - 1;
 
while(count--)
   
*--tmp_dest = *--tmp;
}
return dest;
}

 

void *memcpy(void *dest, const void *source, size_t count)
{
 assert((NULL 
!= dest) && (NULL != source));
 
char *tmp_dest = (char *)dest;
 
char *tmp_source = (char *)source;
 
while(count --)//不对是否存在重叠区域进行判断
   *tmp_dest ++ = *tmp_source ++;
 
return dest;
}

 

 

 

 

 

 

 

 

 

 

 

#ifndef _INI_DOCUMENT_H_ #define _INI_DOCUMENT_H_ #define ASCIILINESZ 1024 //每行文本能容纳文字的最大长度 #define ASCIISECTIONSZ 256 //每个Section的最大长度 #define ASCIIKEYSZ 256 //每个Key的最大长度 #define ASCIIVALUESZ 256 //每个Value的最大长度 #define ASCIIDESCSZ 512 //每个注释的最大长度 #include "RBTree.h" #include "Section.h" #include "Key.h" #include "Value.h" namespace INI { using KeyMap = RBTREE::RBTree<Key, Value>; using SectionMap = RBTREE::RBTree<Section, KeyMap>; class IniDocument { typedef enum class _line_status_ : unsigned char { LINE_ERROR,//未知行 LINE_DESCRIPTION,//注释行 LINE_SECTION,//节点行 LINE_KEY//键值行 } line_status; public: IniDocument(const char* _szFile); ~IniDocument(); KeyMap& operator[](const char* _szSection); void Clear(); private: void load(); line_status parse(char* _szLine, char* _szSection, char* _szKey, char* _szValue, char* _szDesc); private: char* m_szFile;//文件路径 SectionMap m_sectionMap; //存储section的映射 }; } #include "../inc/IniDocument.h" INI::IniDocument::IniDocument(const char* _szFile) { if (!_szFile || _szFile[0] == '\0') { //错误处理: 文件路径不能为空 throw("文件路径不能为空."); } size_t nLen = strlen(_szFile); size_t nSize = ((nLen + 1) + 3) & ~3; if (nLen >= _MAX_PATH) { //错误处理: 文件路径过长 throw("文件路径过长."); } m_szFile = new char[nSize]; memcpy(m_szFile, _szFile, nLen + 1); load(); //加载文件内容到内存 } INI::IniDocument::~IniDocument() { Clear(); } INI::KeyMap& INI::IniDocument::operator[](const char* _szSection) { //返回对应Section的映射 return m_sectionMap[Section(_szSection)]; } void INI::IniDocument::Clear() { if (m_szFile) { delete[] m_szFile; m_szFile = nullptr; } for(SectionMap::iterator it=m_sectionMap.begin();it!=m_sectionMap.end();++it) { //清空每个Section的映射 it->second.Clear(); } m_sectionMap.Clear(); //清空Section映射 } void INI::IniDocument::load() { /*特别注意 读出的ini内容一般都是utf-8,而保存在内存中的内容也是utf-8.直接保存到文件没问题 但如果查找就会出问题。要把查找的中文字串 转成utf8再查找才能找到 但每次查找section或者key都转一次utf8就浪费了 解决方案: 从文件解析出来的utf8内容转回普通char字串。再保存到内存中,这样代码直接就能查找到。 而保存回文件的时候,把内存的字串转回utf8再保存即可 如果不想频繁转换,直接使用ANSI编码文件即可 */ #pragma warning(disable:4996) FILE* pFile = fopen(m_szFile, "r"); #pragma warning(default:4996) if (!pFile) { printf("<IniDocument::load> 打开Ini文件失败,路径: %s\n", m_szFile); return; } char szLineBuffer[ASCIILINESZ] = { 0 };//把文件中的内容逐行读入此内存中 char szSection[ASCIISECTIONSZ] = { 0 };;//把节点解析完成后放入此内存中 char szKey[ASCIIKEYSZ] = { 0 };;//把Key解析完成后放入此内存中 char szValue[ASCIIVALUESZ] = { 0 };;//把Value解析完成后放入此内存中 char szDesc[ASCIIDESCSZ] = { 0 };;//把注释读入此内存中 size_t nLines = 0; //记录当前的行数 size_t nLen = 0; //记录当前行的长度 KeyMap* pKeyMap = nullptr; //用于存储当前解析的Key映射 Desc desc; //用于存储当前解析的全行注释 while (fgets(szLineBuffer, ASCIILINESZ, pFile))//每次读取一行内容 { ++nLines; //行数自增 if (szLineBuffer[0] == '\n')continue;//如果取出来的内容第一个字符就是换行符,无条件取下一行内容 nLen = strlen(szLineBuffer);//更新行内容的字串长度 if (szLineBuffer[nLen - 1] != '\n' && !feof(pFile)) { //如果此行的结束符 不是换行符\n 并且 文件并未结束 -> 如果都满足条件,说明此行肯定是太长了。应当调整行的最大长度。默认:ASCIILINESZ=1024 printf("<IniDocument::load> 行 %zu 内容过长,请检查文件内容.\n", nLines); continue; //跳过此行 } //开始进行字串解析 switch (parse(szLineBuffer, szSection, szKey, szValue, szDesc))//进入解析过程 { case line_status::LINE_ERROR://未知行 break; case line_status::LINE_DESCRIPTION://全行注释 { desc = szLineBuffer; //将注释内容存储到Desc对象中 break; } case line_status::LINE_SECTION://节点 { Section section(szSection); //创建Section对象 if(desc.m_szDesc)section.m_vecDesc[0] = std::move(desc); //将注释内容存储到Section对象中 if (szDesc[0] != '\0')section.m_vecDesc[1] = szDesc; //如果有第二个注释内容,存储到Section对象中 pKeyMap = &m_sectionMap[section]; //获取当前Section映射 break; } case line_status::LINE_KEY://键值 { if (!pKeyMap) { printf("<IniDocument::load> 键值行 %zu 解析失败<没有Section>,请检查文件内容.\n", nLines); continue; //如果没有Section映射,跳过此行 } Key key(szKey); //创建Key对象 Value value(szValue); //创建Value对象 if (desc.m_szDesc) //如果有全行注释内容 { //将注释内容存储到Key对象中 key.m_vecDesc[0] = std::move(desc); } if(szDesc[0] != '\0') //如果有行块注释内容 { key.m_vecDesc[1] = szDesc; //将行块注释内容存储到Key对象中 } (*pKeyMap)[key] = std::move(value); //将Key和Value存储到当前Section映射中 break; } default: break; } szLineBuffer[0] = 0; //清空行内容 szSection[0] = 0; //清空Section内容 szKey[0] = 0; //清空Key内容 szValue[0] = 0; //清空Value内容 szDesc[0] = 0; //清空注释内容 } fclose(pFile); //关闭文件 } INI::IniDocument::line_status INI::IniDocument::parse(char* _szLine, char* _szSection, char* _szKey, char* _szValue, char* _szDesc) { #pragma warning(disable:4996) line_status sta = line_status::LINE_ERROR;//分析结果 size_t nCount = 0; //记录sccanf匹配的数量 if (_szLine[0] == '#' || _szLine[0] == ';')//开头就是单行注释的解析符 {//说明全行都是注释 sta = line_status::LINE_DESCRIPTION; } //开始解析节点 Section //(必须遵守一个规则,从有到无,比如结尾带分号的应该先做匹配。 //然后再到结尾不带分号的。因为如果先匹配不带分号的,带分号的也会匹配得上。就会莫名多个分号了) //例如: key = value; 如果先匹配无分号的,那么值就是 value; 达不到预期效果,结尾多了个分号 else if ( //以下匹配 节点(后面带注释) (nCount = sscanf(_szLine, "[%[^]]]%[^\n]", _szSection, _szDesc)) == 2 ||//例子: [section01]#this is description 01或者[section01];this is description 01 (nCount = sscanf(_szLine, "[%[^]]]%[^\n]", _szSection, _szDesc)) == 1//例子: [section] ) { sta = line_status::LINE_SECTION; } //开始解析键值 key = value else if ( //(必须遵守一个规则,从有到无,比如结尾带分号的应该先做匹配。然后再到结尾不带分号的。因为如果先匹配不带分号的,带分号的也会匹配得上。就会莫名多个分号了) (nCount = sscanf(_szLine, "%[^=]=%[^;]%[^\n]", _szKey, _szValue, _szDesc)) == 3 || //例子: key1=value1;this is description 01 (nCount = sscanf(_szLine, "%[^=]=%[^#]%[^\n]", _szKey, _szValue, _szDesc)) == 3 ||//例子: key2=value2#this is description 01 (nCount = sscanf(_szLine, "%[^=]=%[^\n]", _szKey, _szValue)) == 2//例子: key1=value1 ) { sta = line_status::LINE_KEY; } #pragma warning(default:4996) return sta; } 总体架构就是这样的,帮我分析一下有没有问题
最新发布
07-25
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值