C++学习之路(一):const与define,结构体对齐,new/delete

C++进阶技巧
本文深入探讨了C++中的const与define的区别、结构体对齐原理及其优化方法,以及new/delete操作符的正确使用方式。

前言:针对C++ Primer和Effective C++两本书,以及技术博客与实验测试,本系列主要是针对C++进行系统化学习,记录学习中遇到的问题和经验。

(一)const与define

关于const与define的用法与比较,可以详见我之前的这篇文章,在这里就不再赘述了。

 

(二)结构体对齐

简单说一下为什么需要结构体对齐,或者说内存对齐。

在32位操作系统中,一次寻址能够取得的指令长度是4字节(也就是32位地址)。对于一个4字节变量来说,如果其存放的位置在正好在一个4字节中,那

对于操作系统而言,只需要一次访存就可以获得。如果其分存在两个4字节地址,则系统需要先进行一次访存取得前一块内存中的数据,再进行一次访存取得

后一块内存中的数据。显然这样带来的问题,即影响了程序运行的性能。

 

总结一下:

什么是内存对齐:

1)编译器为每个数据单元排在合适的内存地址上

为什么需要内存对齐:

1)提高性能

如何对齐:

1)第一个数据成员放在内存偏移量为0的地址;

2)接下来的数据成员放在4字节整数倍的地址;

3)如果是结构体,结构体整体也会进行内存对齐

 

给两个例子:

struct test1
{
    char a;
    int b;
    char c;
}

struct test2
{
    char a;
    char b;
    int c;
}

cout<<sizeof(test1)<<endl;
cout<<sizeof(test2)<<endl;

其中sizeof(test1)的结果为12,sizeof(test2)的结果为8,简单分析一下:对于test1来说,a占一个字节,接下来的b占4个字节,为了保证内存对齐,b需要一个完整的4字节大小内存,

一个示意图为(1表示占用,0表示未占用)

a: 1 0 0 0

b: 1 1 1 1

c: 1 0 0 0

整个结构体大小为12个字节。

 

再看test2,a占一个字节,b占一个字节,c占4个字节,在这种情况下,内存分配的示意图为

a,b : 1 1 0 0

c:     1 1 1 1

整个结构体大小为8个字节

总结一下:在结构体变量声明中,为减少内存开销,应该尽可能的将内存小的变量先定义,内存大的变量后定义。

 

(三)new和delete

(1)new/delete 与 malloc/free

new和delete是c++中的运算符,malloc和free是c中的库函数。它们的本质比较类似,从进程地址空间的堆区分配一块内存,返回内存首地址,但new和delete可以用于分配类对象的地址

空间,这个对于malloc和free来说不行。

(2)new 与 new [5]

int *p = new int(5);   //分配一个int对象,数据为5

int *q = new int[5];   //分配5个int对象

(3)delete 与 delete []

delete主要针对一个对象,将其内存进行释放。

delete []主要针对对象数组,将其数组内存进行释放。

注意:上述(2)中q,如果采用delete q,仅仅只会释放q[0]所在的内存区域,从而造成内存泄露,正确的方式应该采用delete []q;

转载于:https://www.cnblogs.com/scu-cjx/p/8572601.html

#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、付费专栏及课程。

余额充值