堆和栈的8大区别——C++命名空间——内存管理
《引言》
古之欲明明德与天下者,先知其国。欲治其国者,先齐其家。欲齐其家者,先修其身。欲修其身者,先正其心。欲正其心者,先诚其意。欲诚其意者,先致其知。致知在格物。
1·命名空间——namespace/using
1·1命名空间出现的目的
大型程序开发会使用多个独立开发的库,当汇聚到一起时,会出现全面的名字,类,函数和模板,名字冲突情况。多个库放在一起会引发 命名空间污染
**命名空间(namespace)**将命名空间分割了全局命名空间,其中每个命名空间时要给作用域。通过某个命名空间中定义库的名字,库的作者可以避免全局名字固有的限制
1·2命名空间的定义
关键字+名字
namespace HWK{
int a;
int b;
int c;
}
只要能出现在全局作用域中的声明就可以置于命名空间内
命名空间作用域之后无须分号
1·3 ::作用域解析符来进行对应访问
HWK::a;
#include<iostream>
#include <vector>
using namespace std;
namespace HWK {
int a;
int b;
int c;
}
int main() {
cin >> HWK::a;
cout << HWK::a;
return 0;
}
1·4命名空间可以不连续
如果已经存在了与现在命名空间相同的一个空间,那么新的空间名字将会追加在其后面
#include<iostream>
#include <vector>
using namespace std;
namespace HWK {
int a;
}
namespace HWK {
int k;
}
int main() {
cin >> HWK::k;
cout << HWK::k;
return 0;
}
1.5嵌套的命名空间
#include<iostream>
#include <vector>
using namespace std;
namespace HWK {
int a;
//嵌套命名空间
namespace HWK {
int k;
}
}
int main() {
//嵌套访问
cin >> HWK::HWK::k;
cout << HWK::HWK::k;
return 0;
}
1.6未命名的命名空间
#include<iostream>
#include <vector>
using namespace std;
namespace {
int n;
}
int main() {
cin >> n;
cout << n;
return 0;
}
未命名的名字空间仅在特定的文件内部有效,其作用范围不会横跨多个不同文件
其空间中定义的变量拥有静态生命周期,它们在第一次使用前创建,并且直到程序结束之后才销毁,同时它与全局变量名不能够重复
1.7使用using将名字空间成员引入
#include <iostream>
using namespace std;
namespace HWK{
int n;
int m;
int HWK;
}
using HWK::m;//如果注释掉这一行,则报错出现错误,在这里引入的时候一定要注意,要加上作用域解析符
int main(){
cin>>m;
cout<<m;
return 0;
}
1.8 使用using namespace 将名字空间引入
#include <iostream>
using namespace std;
namespace HWK{
int n;
int m;
int HWK;
}
//在这里引入空间,那么就可以直接使用了
using namespace HWK;
int main(){
cin>>m;
cout<<m;
return 0;
}
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
#include <stdlib.h>
#include <iostream>
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;
}
2·1·3·2自己通过malloc实现calloc函数
#include <stdlib.h>
#include <iostream>
#include <cstring>
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 <stdlib.h>
#include <iostream>
#include <cstring>
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 <iostream>
#include<stdlib.h>
#include<malloc.h>
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 <iostream>
#include<stdlib.h>
#include<malloc.h>
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内存追加的两种情况
- 上图这种比较顺利的情况,在第一次申请的后面刚好有 有需要大小的空间,加起来刚好够
- 后面的部分不够了,那么realloc()就会重新找一块儿内存,并搬移过去之前的数据
2.2 C++ 中的new和delete
运算符new分配内存,delete释放new分配的内存
2.2.1运算符new–分配内存空间
自由的空间是无名的,因此new无法为其分配的对象命名,所以返回的是一个指向该对象的指针
2.2.1.1⭐动态内存分配的对象最好进行初始化
//此 new在堆空间构造一个int类型的对象,并返回该对象的指针
int *pi=new int;//pi指向一个动态分配的、未初始化的无名对象
string *ps=new string;//ps指向一个动态分配的,未初始化的无名对象
#include<iostream>
#include<vector>
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<int>* pv = new vector<int>{ 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;
}
2.1.1.2通过auto来初始化new的类型–不过要注意,只有单一初始化时才可以使用auto
#include<iostream>
#include<vector>
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;
2.1.2.3动态分配const对象
动态分配的const对象必须进行初始化。对于一个定义了默认构造函数的类 类型,其const动态对象可以隐式初始化,而其他类型的对象必须显示初始化。
分配的对象是const所以返回的指针也是一个指向const类型的指针
#include<iostream>
#include<vector>
using namespace std;
int main() {
const int* pi = new const int;
const string* ps = new const string;
return 0;
}
2.2.2通过函数形式使用new–与malloc类似
#include<iostream>
#include<vector>
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;
}
2.3.3定位new
如果内存被耗尽,new申请内存失败,则会抛出一个类型为bad_alloc的异常–我们称之为定位new
如果不想抛出 异常要求返回一个空指针可以使用 nothrow来将其返回值 修改成空指针
#include<iostream>
#include<vector>
#include<new>
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;
}
2.3.4.4 对于内置类型 new / delete / malloc/free 可以混用
#include<iostream>
#include<vector>
#include<new>
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释放 | 只能动态分配并且手动释放 |
分配效率 | 底层提供支持,分配专门的寄存器存放栈地址,压栈出栈由专门指令执行,因此效率高 | 由函数库提供,机制复杂效率低 |
分配后系统响应 | 只要栈剩余空间大于所申请空间,系统将为程序提供内存,否则报告异常提示栈溢出 | 操作系统提供记录一个空闲内存地址的链表 |
碎片问题 | 不存在碎片问题,因为栈是先进后出,内存块弹出栈之前,上面后进栈内容已经弹出 | 频繁申请释放会造成内存空间不连续,从而造成大量碎片,使其使用效率降低 |