这里是目录标题
- 前言
- 动态内存分布
- 如何理解C++语法的增加
- new
- new用法
- 关于struct和class的使用
- 关于free和delete的区别。
- 背会这句话
- 抛异常
- operator new和operator delete
- 内存池
- new和delete原理
- 定位new
- malloc和new的区别是什么?
- 内存泄漏
前言
总结复习前面的知识。
注意:学了C++在做oj题目时,不一定非要用类。
可以多项选择最简单的就行。怎么简单怎么写。
可以用类名定义一个匿名对象。
匿名对象的声明周期只在那一行。
weight();//匿名对象,匿名周期就是当前行。
假如构造+拷贝构造在一起运行,则编译器会优化步骤。
f(weight());//先构造匿名对象weight(),然后再传参拷贝构造。
结论:一个表达式中,连续步骤的构造+拷贝构造
或者拷贝构造+拷贝构造。胆大的编译器都会优化,合二为一。
动态内存分布
最上面是栈。然后是堆,静态区(系统叫做数据段),常量区(代码段)。这里的代码不是我们写的代码,而是那些指令。
栈和堆是程序运行起来才开辟空间的。
为什么要区分这些区域?
因为不同数据需要存在不同的位置。
int glob = 1;
static int staticglob = 1;
void test()
{
static int staticvar = 1;
int loacalvar = 1;
int num1[10] = {1,2,3,4};
char char2[] = "abcd";
char* pchar3 = "abcd";
int* ptr1 = (int*)malloc(sizeof(int)*4);
int* ptr2 =(int*)calloc(4,sizeof(int));
int* ptr3 = (int*)realloc(ptr2,sizeof(int)*4);
}
globalvar是全局变量,全局变量从语言的角度看在静态区。从系统看在数据段。
staticgob是静态变量,也在静态区。
staticvar是静态变量,在静态区
总结:全局变量和静态变量的声明周期都是全局的。所以都在静态区。
localvar是局部变量,局部变量是在栈上开辟的。
num1代表的是这个数组,数组是在栈上的。
char2在栈上。char2是一个数组,这个数组在栈上。他是用常量字符串abcd ** 拷贝 过去初始化他的。注意这个拷贝**。
*char2在也是在栈上。因为它代表的是这个数组指向的内容,内容也在栈上。
pchar3也在栈上
*pchar3解引用得到是常量区的字符串abcd所以在常量区。
ptr1在栈上。
*ptr1在堆上。因为他是malloc开辟的。
const修饰的叫做常变量,常变量是在栈上。
如何理解C++语法的增加
比如函数重载,引用,new/delete,流插入和流提取运算符。类和对象。
都是为了弥补C语言的不足。可能C++的发明者当时用C语言写
new
malloc和calloc的区别是什么呢?
malloc是开空间。
calloc是开空间+初始化。
realloc是扩容,有空间原地扩,无空间,异地扩。
C语言已经有了开空间的方法。C++为什么搞了一个new?
new和delete配合使用。
new是为了解决C语言中不好用的地方。
new用法
new是先开空间,再调用构造函数进行初始化。,
对于内置类型而言,用malloc和new除了用法不同,没有什么区别,他们的区别在于自定义类型。
//new 一个对象
int* p1 = new int;
//new 10个对象
int* p2 = new int[10];
//new一个对象初始化为10
int* p3 = new int(10);
//C++11的数组初始化
int* p4= new int[10]{10,1,2,3};
//初始化时一定要匹配释放。
delete p1;
delete[] p2;
delete p3;
delete[] p4;
对于自定义类型链表创建结点来说。
struct ListNode
{
ListNode* next;
int val;
//构造函数
ListNode(int val = 0)
:_next(nullptr)
,_val(val)
{}
};
以下是C语言的用法,特别麻烦。
但是对于C++来说。C++直接用new就行,因为new会自动调用构造函数。假如构造函数写了就会自动初始化。
ListNode* n = new ListNode;
关于struct和class的使用
struct里面默认都是共有的,一般都要访问的设置为公有,比如list的节点ListNode。
class里面成员默认都是私有的。
关于free和delete的区别。
free直接把指向的当前指针释放。而不管指针的指针,容易导致内存泄漏。
而delete会进行以下两个步骤。
1.先调用析构函数,可以在释放析构函数里面的指针等资源,让空间进行释放。交还给操作系统。
2.再释放当前指针所指向的空间。
背会这句话
创建对象的时候会调用构造函数,假如没写,就是有默认构造函数。对于内置类型来说不做处理,对于自定义类型调用他的默认构造函数。
抛异常
1.对于C语言我们开辟空间失败需要检查,加上assert,malloc失败会返回空指针。
2.对于C++的new来说开辟空间失败会抛异常。
怎么捕获异常?
异常的原理是根据继承和多态实现的。
例如以下代码就是抛异常
try和catch会二选一执行。抛异常就执行catch。
try
{
void* p2 = new char[124*1024*1024];
}
catch(const exception& e)
{
cout << e.what() << endl;
}
那我们平常使用new都需要抛异常吗?
答案是不需要的。异常一般是在函数外面检查异常的。
operator new和operator delete
重点:new的底层原理是调用operator new和构造函数。
千万要注意。这个虽然带了operator这个词,但不是重载new。
实际这个operator new封装了malloc,malloc失败抛异常。
operator封装了free。
这两个本质和malloc有什么区别吗?实际上没有区别。功能上一样,失败以后不抛异常。
开空间例子。
Stack* st = (Stack*)operator new(sizeof(Stack));
operator delete(st);
那operator new有什么用呢?
是为了new而设计的。
new会调用operator new和构造函数。!!
operator new的作用是调用malloc,假如malloc然后抛异常。假如成功则继续进行构造函数。
构造函数的作用是初始化。
内存池
假如我们频繁的向堆申请和释放空间,这时候可以用一个内存池,因为内存池更快,可以提高效率。
因为堆他中间需要调用operator new,operator new还需要再调用mallo成本很高。
而内存池里面的内存就可以直接用,效率很快。
C++的重载new就可以解决直接调用new而导致效率低下的问题。
就是把new进行重载。来调用内存池。
把delete进行重载,直接调用内存池。
new和delete原理
new
-
调用operator new函数申请空间
-
在申请的空间上执行构造函数,完成对象的构造
delete的原理
- 在空间上执行析构函数,完成对象中资源的清理工作
- 调用operator delete函数释放对象的空间#
new T[N]的原理
- 调用operator new[]函数,在operator new[]中实际调用operator new函数完成N个对象空间的申请
- 在申请的空间上执行N次构造函数
delete[]的原理
- 在释放的对象空间上执行N次析构函数,完成N个对象中资源的清理
- 调用operator delete[]释放空间,实际在operator delete[]中调用operator delete来释放空间
定位new
针对一个空间,显示调用构造函数进行初始化
例子
Stack* obj = (Stack*)operator new(sizeof(Stack));
new(obj)Stack(4);
//相当于
Stack* obj = new Stack(4);
malloc和new的区别是什么?
怎么考虑这个问题呢?
1.从用法上的区别
2.从底层的区别
malloc和free是函数,new和delete是操作符。
底层上的区别就是new会调用构造函数。
free会调用析构函数。
内存泄漏
什么是内存泄漏?
内存泄漏的意思是指针丢了。 是将内存还给了操作系统。
内存泄漏真正的危害是什么?
普通的内存泄漏我们不用怕,就算我们不释放。正常的程序结束,内存就会还给操作系统。
怕的是什么?
怕的是我们的电脑内存很小,或者长期运行的程序,这时候用的时候需要delete。