C++堆和栈的8大区别&&C++内存管理+namespace命名空间

堆和栈的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++中的内存管理

ORl8gK.png

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的区别
  1. 内存申请了之后一定要进行判空👌👌👌
  2. malloc申请的堆区内存初始化为随机值
  3. 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;
}

ORUmD0.png

ORNTHK.png

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()释放必须全部释放-不能释放一半

ORgszV.png

#include <stdlib.h>
#include <iostream>
#include <cstring>
int main(){
    int n=10;
    int *p=(int *) malloc(sizeof(int)*n);//申请40个字节空间的大小
    free(p);//必须全部释放
}

ORh9hj.png

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);//必须全部释放
}

ORh25Q.png

ORhTbT.png

2·3·1·4 内存泄漏的两种情况
  1. 一直在堆区申请内存空间,但从来没有进行释放
  2. 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;
}

OIkCcD.png

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;
}

我们来看下实验结果:

OIAXJx.png

在这里我们注意其内存地址,现在是 0X0127CA60

我们再来看下当我们追加之后ip的地址和大小

通过下面的代码,我们可以看到,刚刚好10个大小,并且内存还是地址没有变-

OIELng.png

2.1.3.6realloc内存追加的两种情况
  1. 上图这种比较顺利的情况,在第一次申请的后面刚好有 有需要大小的空间,加起来刚好够
  2. 后面的部分不够了,那么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;
}

OINkA1.png

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;

OIdQHJ.png

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;

}

OI0g0g.png

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释放只能动态分配并且手动释放
分配效率底层提供支持,分配专门的寄存器存放栈地址,压栈出栈由专门指令执行,因此效率高由函数库提供,机制复杂效率低
分配后系统响应只要栈剩余空间大于所申请空间,系统将为程序提供内存,否则报告异常提示栈溢出操作系统提供记录一个空闲内存地址的链表
碎片问题不存在碎片问题,因为栈是先进后出,内存块弹出栈之前,上面后进栈内容已经弹出频繁申请释放会造成内存空间不连续,从而造成大量碎片,使其使用效率降低
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

HANWEN KE

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值