堆和栈的8大区别——C++命名空间——内存管理
《引言》
古之欲明明德与天下者,先知其国。欲治其国者,先齐其家。欲齐其家者,先修其身。欲修其身者,先正其心。欲正其心者,先诚其意。欲诚其意者,先致其知。致知在格物。
- 1·命名空间——namespace/using
- 1·1命名空间出现的目的
- 1·2命名空间的定义
- 1·3 ::作用域解析符来进行对应访问
- 1·4命名空间可以不连续
- 1.5嵌套的命名空间
- 1.6未命名的命名空间
- 1.7使用using将名字空间成员引入
- 1.8 使用using namespace 将名字空间引入
- 2·C/C++中的内存管理
- 2·1-C语言的动态内存管理
- 2·1·1什么是动态内存
- 2·1·2动态内存管理函数
- 2·1·3动态内存管理的四个函数:malloc,calloc,realloc,free
- 2·1·3·1malloc与calloc的区别
- 2·1·3·2自己通过malloc实现calloc函数
- 2·1·3·3 malloc申请内存的的底层申请与越界
- 2·3·1·4 内存泄漏的两种情况
- 2·1·3·5 free()使用的注意事项
- 2·1·3·6 realloc()
- 2.1.3.6realloc内存追加的两种情况
- 2.2 C++ 中的new和delete
- 2.2.1运算符new–分配内存空间
- 2.2.1.1⭐动态内存分配的对象最好进行初始化
- 2.1.1.2通过auto来初始化new的类型–不过要注意,只有单一初始化时才可以使用auto
- 2.1.2.3动态分配const对象
- 2.2.2通过函数形式使用new–与malloc类似
- 2.3.3定位new
- 2.3.4.4 对于内置类型 new / delete / malloc/free 可以混用
- 3.⭐⭐⭐栈和堆的8大区别
大型程序开发会使用多个独立开发的库,当汇聚到一起时,会出现全面的名字,类,函数和模板,名字冲突情况。多个库放在一起会引发 命名空间污染
**命名空间(namespace)**将命名空间分割了全局命名空间,其中每个命名空间时要给作用域。通过某个命名空间中定义库的名字,库的作者可以避免全局名字固有的限制
1·2命名空间的定义关键字+名字
namespace HWK{
int a;
int b;
int c;
}
只要能出现在全局作用域中的声明就可以置于命名空间内
命名空间作用域之后无须分号
1·3 ::作用域解析符来进行对应访问HWK::a;
#include1·4命名空间可以不连续#include using namespace std; namespace HWK { int a; int b; int c; } int main() { cin >> HWK::a; cout << HWK::a; return 0; }
如果已经存在了与现在命名空间相同的一个空间,那么新的空间名字将会追加在其后面
#include1.5嵌套的命名空间#include using namespace std; namespace HWK { int a; } namespace HWK { int k; } int main() { cin >> HWK::k; cout << HWK::k; return 0; }
#include1.6未命名的命名空间#include using namespace std; namespace HWK { int a; //嵌套命名空间 namespace HWK { int k; } } int main() { //嵌套访问 cin >> HWK::HWK::k; cout << HWK::HWK::k; return 0; }
#include#include using namespace std; namespace { int n; } int main() { cin >> n; cout << n; return 0; }
未命名的名字空间仅在特定的文件内部有效,其作用范围不会横跨多个不同文件
其空间中定义的变量拥有静态生命周期,它们在第一次使用前创建,并且直到程序结束之后才销毁,同时它与全局变量名不能够重复
1.7使用using将名字空间成员引入#includeusing namespace std; namespace HWK{ int n; int m; int HWK; } using HWK::m;//如果注释掉这一行,则报错出现错误,在这里引入的时候一定要注意,要加上作用域解析符 int main(){ cin>>m; cout< 1.8 使用using namespace 将名字空间引入 #includeusing namespace std; namespace HWK{ int n; int m; int HWK; } //在这里引入空间,那么就可以直接使用了 using namespace HWK; int main(){ cin>>m; cout< 2·C/C++中的内存管理 2·1-C语言的动态内存管理 栈是由系统管理-在Windows其默认大小为1M,在Linux上的大小是10M
1M =1024K*1024K
在vs2019当中可以通过设置来修改栈的大小
2·1·1什么是动态内存在内存的-堆区申请空间–完全由程序人员管理
2·1·2动态内存管理函数程序运行的过程中申请一定大小的内存,使用完成之后,还给操作系统
空间申请完了,一定要进行释放
释放完了之后一定要置为空
2·1·3动态内存管理的四个函数:malloc,calloc,realloc,free需要引入stdlib.h或malloc.h函数
2·1·3·1malloc与calloc的区别
- 内存申请了之后一定要进行判空
- malloc申请的堆区内存初始化为随机值
- calloc申请的堆区内存初始化为0
#include2·1·3·2自己通过malloc实现calloc函数#include using namespace std; //c的动态内存管理 int main(){ int n=10; int *ipa=(int *) malloc(sizeof(int)*n);//初始化为随机值 int *ipb=(int *) calloc(n, sizeof(int));//初始化为0; // ipa=(int *) realloc(ipa, sizeof(int)*n*2); free(ipa); ipa= NULL; free(ipb); ipb=NULL; return 0; } #include#include #include using namespace std; //size_t类型表示C中任何对象所能达到的最大长度,是无符号整型 void * My_calloc(::size_t num,size_t size){ void *vp= malloc(num*size); if(vp!=NULL){ memset(vp,0,num*size); } return vp; } //c的动态内存管理 int main(){ int n=10; int *ipa=(int *) malloc(sizeof(int)*n);//初始化为随机值 if(ipa==NULL){ exit(1); } int *ipb=(int *) My_calloc(n, sizeof(int));//初始化为0; if(ipb==NULL){ exit(1); } // ipa=(int *) realloc(ipa, sizeof(int)*n*2); free(ipa); ipa= NULL; free(ipb); ipb=NULL; return 0; } //callloc 的两个参数 int* ip = (int*)calloc(10, sizeof(int));2·1·3·3 malloc申请内存的的底层申请与越界free()释放必须全部释放-不能释放一半
#include#include #include int main(){ int n=10; int *p=(int *) malloc(sizeof(int)*n);//申请40个字节空间的大小 free(p);//必须全部释放 } int main() { int n = 10; int* p = (int*)malloc(sizeof(int) * n);//申请40个字节空间的大小 //当i<=n的时候将会覆盖下越界标记,在释放时将会报错 for (int i = 0; i <= n; i++) { p[i] = i; } free(p);//必须全部释放 }2·3·1·4 内存泄漏的两种情况
- 一直在堆区申请内存空间,但从来没有进行释放
- malloc申请了内存空间的地址,丢失了其地址,找不到申请空间的开始地址
程序结束(进程结束之后)操作系统自动回收
2·1·3·5 free()使用的注意事项free(ipa):是将其指向的空间还给系统(将标记修改为0),同时进行对应的填充dd,以防后面的程序可以读取当中的内容,加强安全性
⚠️⚠️⚠️free()之后指针一定一定置为NULL
⚠️⚠️⚠️free()之后指针一定一定置为NULL
⚠️⚠️⚠️free()之后指针一定一定置为NULL
对于同一个空间,不能够释放两次,如果释放两次则将报错
2·1·3·6 realloc()我们通过下面代码,申请了大小位20个字节的空间,那么realloc()是怎么回事呢?
#include#include #include using namespace std; int main() { int n = 5; int* ip = (int*)malloc(sizeof(int) * n); if (ip == nullptr) { exit(1); } for (int i = 0; i < n; i++) { ip[i] = i; } int m = 10; return 0; } realloc()是在ip的后面继续追加对应的内存,那在这里我们追加ip两倍的内存
#include#include #include using namespace std; int main() { int n = 5; int* ip = (int*)malloc(sizeof(int) * n); if (ip == nullptr) { exit(1); } for (int i = 0; i < n; i++) { ip[i] = i; } int m = 10; ip = (int*)realloc(ip,sizeof(int)*2); free(ip); ip=nullptr; return 0; } 我们来看下实验结果:
在这里我们注意其内存地址,现在是 0x0127CA60
我们再来看下当我们追加之后ip的地址和大小
通过下面的代码,我们可以看到,刚刚好10个大小,并且内存还是地址没有变-
2.1.3.6realloc内存追加的两种情况2.2 C++ 中的new和delete
- 上图这种比较顺利的情况,在第一次申请的后面刚好有 有需要大小的空间,加起来刚好够
- 后面的部分不够了,那么realloc()就会重新找一块儿内存,并搬移过去之前的数据
运算符new分配内存,delete释放new分配的内存
2.2.1运算符new–分配内存空间自由的空间是无名的,因此new无法为其分配的对象命名,所以返回的是一个指向该对象的指针
2.2.1.1⭐动态内存分配的对象最好进行初始化//此 new在堆空间构造一个int类型的对象,并返回该对象的指针 int *pi=new int;//pi指向一个动态分配的、未初始化的无名对象 string *ps=new string;//ps指向一个动态分配的,未初始化的无名对象#include2.1.1.2通过auto来初始化new的类型–不过要注意,只有单一初始化时才可以使用auto#include using namespace std; int main() { //初始化的几种方式 int* pi = new int(1024);//pi指向的对象为1024 string* ps = new string(10, 9);//*ps为"9999999999" //vector有10个元素一次从0到9 vector * pv = new vector { 0,1,2,3,4,5,6,7,8,9 }; string* ps1 = new string;//默认初始化为空 string* ps2 = new string();//值初始化为空 int *pi1 = new int;//默认初始化;*pil的值未定义 int* pi2 = new int();//值初始化为0;*pi2的值为0 delete(pi); delete(ps); delete(pv); delete(ps1); delete(ps2); delete(pi1); delete(pi2); return 0; } #include2.1.2.3动态分配const对象#include using namespace std; int main() { int obj = 0;//在这里auto推导时,一定要有初始值 auto p1 = new auto(obj); int a = 0, b = 0, c = 0; auto p2 = new auto(a, b, c);//error --编译器不通过 return 0; } //p1的类型是一个指针,指向从obj自动推断出的类型。若obj是一个整形,则它为整型,如果为string ,则它为string * 新分配的对象用 obj初始化 比如 Obj为 10 那么*p1=10; 动态分配的const对象必须进行初始化。对于一个定义了默认构造函数的类 类型,其const动态对象可以隐式初始化,而其他类型的对象必须显示初始化。
分配的对象是const所以返回的指针也是一个指向const类型的指针
#include2.2.2通过函数形式使用new–与malloc类似#include using namespace std; int main() { const int* pi = new const int; const string* ps = new const string; return 0; } #include2.3.3定位new#include using namespace std; int main() { int n = 10; int* ip = (int*)::operator new(sizeof(int) * 10); int* ip1 = (int*)::operator new(sizeof(int)); delete (ip); return 0; } 如果内存被耗尽,new申请内存失败,则会抛出一个类型为bad_alloc的异常–我们称之为定位new
如果不想抛出 异常要求返回一个空指针可以使用 nothrow来将其返回值 修改成空指针
#include2.3.4.4 对于内置类型 new / delete / malloc/free 可以混用#include #include using namespace std; //定位new int main() { int* p1 = new int;//如果分配失败,new抛出 std::bad_alloc int* p2 = new(nothrow)int;//如果分配失败,new 返回一个空指针 delete p1; p1 = nullptr; delete p2; p2 = nullptr; return 0; } #include#include #include using namespace std; //定位new int main() { int* p1 = new int(1024); int *p2=(int *)malloc(sizeof(int)*10); free (p1); p1 = nullptr; delete p2; p2 = nullptr; return 0; } 两点注释事项
p2 = nullptr; delete p2;//可以释放是个空指针 int i; delete i;//error 不能够释放一个不是指针的对象3.⭐⭐⭐栈和堆的8大区别
区别 栈 堆 管理方式 操作系统自动管理 程序员控制,使用方便,容易产生内存泄漏 生长方式 向下分配-由高地址,向低地址扩展,连续的内存区域 向上分配-由低地址向高地址扩展-不连续的内存区域 空间大小 由系统预先预定(默认1M或10M) 受限于计算机系统的有效虚拟内存,32位Linux可达2.9G 存储内容 栈在函数调用时,先压入函数实参,然后主调函数中下条指定地址入栈,最后时被调函数的局部变量.调用结束后,局部变量先出栈,指令地址出战,最后栈平衡 通常在头部用要一个字节存放其大小,堆用于存储生存期与函数调用无关的数据,具体由程序员自己安排 分配方式 栈可静态分配或动态分配.静态分配由编译器完成,如局部变量的分配.动态分配由alloca函数在栈上申请空间,用完后不用调用free释放 只能动态分配并且手动释放 分配效率 底层提供支持,分配专门的寄存器存放栈地址,压栈出栈由专门指令执行,因此效率高 由函数库提供,机制复杂效率低 分配后系统响应 只要栈剩余空间大于所申请空间,系统将为程序提供内存,否则报告异常提示栈溢出 操作系统提供记录一个空闲内存地址的链表 碎片问题 不存在碎片问题,因为栈是先进后出,内存块弹出栈之前,上面后进栈内容已经弹出 频繁申请释放会造成内存空间不连续,从而造成大量碎片,使其使用效率降低



