new与delete
new与delete new:向内存申请一块空间,并创建对象。 delete:释放 new 申请的空间,并销毁对象。 它们主要用于 动态内存管理 。 1.new 平时写: 这个变量一般存在于 栈区 ,函数结束后自动销毁。 但有时候我们需要: 这时候 new int 会在 堆区 申请一个 int 大小...
new与delete
new:向内存申请一块空间,并创建对象。delete:释放new申请的空间,并销毁对象。
它们主要用于 动态内存管理。
1.new
平时写:
int x = 10;
这个变量一般存在于 栈区,函数结束后自动销毁。
但有时候我们需要:
int* p = new int;
这时候 new int 会在 堆区 申请一个 int 大小的空间,返回这个空间的地址。
基本写法
申请一个普通变量
int* p = new int;
*p = 10;
cout << *p << endl;
delete p;
申请时直接初始化
int* p = new int(10);
cout << *p << endl;
delete p;
等价于:
int* p = new int;
*p = 10;
2.delete
delete 用来释放 new 申请的内存。
如果你只写:
int* p = new int(10);
但是不写:
delete p;
那么这块内存就一直占着,程序无法再使用它。
这叫 内存泄漏。
比如:
void f() {
int* p = new int(10);
}
这里函数结束后,p 这个指针变量消失了。
但是 new int(10) 申请出来的那块堆内存还在。
问题是:
p 没了
所以你再也找不到那块内存了,也就无法释放。
这就是内存泄漏。
3.数组
动态申请数组:
int* a = new int[5];
这表示申请 5 个 int 的连续空间。
释放数组必须写:
delete[] a;
完整代码:
#include <iostream>
using namespace std;
int main() {
int* a = new int[5];
for (int i = 0; i < 5; i++) {
a[i] = i + 1;
}
for (int i = 0; i < 5; i++) {
cout << a[i] << " ";
}
delete[] a;
return 0;
}
输出:
1 2 3 4 5
4.new与delete基本规则
| 申请方式 | 释放方式 |
|---|---|
new int | delete p |
new int[10] | delete[] p |
new Student | delete p |
new Student[10] | delete[] p |
5.new 对象时会调用构造函数
假设有一个类:
#include <iostream>
using namespace std;
class Student {
public:
Student() {
cout << "构造函数调用" << endl;
}
~Student() {
cout << "析构函数调用" << endl;
}
};
int main() {
Student* p = new Student;
delete p;
return 0;
}
输出:
构造函数调用
析构函数调用
这说明:
new Student
不仅申请内存,还会调用构造函数。
delete p
不仅释放内存,还会调用析构函数。
6. new 对象数组
Student* arr = new Student[3];
delete[] arr;
这里会调用 3 次构造函数,释放时调用 3 次析构函数。
例子:
#include <iostream>
using namespace std;
class Student {
public:
Student() {
cout << "构造函数" << endl;
}
~Student() {
cout << "析构函数" << endl;
}
};
int main() {
Student* arr = new Student[3];
delete[] arr;
return 0;
}
输出类似:
构造函数
构造函数
构造函数
析构函数
析构函数
析构函数
7. new int 和 new int()
这个细节容易混。
int* p1 = new int;
int* p2 = new int();
区别:
new int
申请空间,但不初始化,里面是随机值。
new int()
申请空间,并初始化为 0。
例子:
int* p1 = new int;
int* p2 = new int();
cout << *p1 << endl; // 随机值
cout << *p2 << endl; // 0
也可以指定值:
int* p = new int(100);
8. 数组初始化
int* a = new int[5];
这 5 个数没有初始化,可能是随机值。
int* b = new int[5]();
这 5 个数都会初始化为 0。
例子:
int* a = new int[5]();
相当于:
a[0] = 0;
a[1] = 0;
a[2] = 0;
a[3] = 0;
a[4] = 0;
9. delete 后指针会怎样?
比如:
int* p = new int(10);
delete p;
执行完 delete p 后,堆区的内存被释放了。
但是注意:
p 本身仍然保存着原来的地址
它变成了一个 悬空指针。
比如:
cout << *p << endl; // 危险
这时候 p 指向的内存已经无效了,再访问就是未定义行为。
所以推荐写:
delete p;
p = nullptr;
完整写法:
int* p = new int(10);
delete p;
p = nullptr;
这样后面如果误用:
if (p != nullptr) {
cout << *p << endl;
}
delete nullptr 是安全的
下面这样没问题:
int* p = nullptr;
delete p;
不会报错。
10.不能重复 delete
错误写法:
int* p = new int(10);
delete p;
delete p; // 错误
第一次 delete 已经释放了内存,第二次再释放同一块内存,叫 重复释放,很危险。
正确:
int* p = new int(10);
delete p;
p = nullptr;
delete p; // 安全,因为 delete nullptr 没事
11. new 失败?
如果内存不够,普通的 new 会抛出异常:
std::bad_alloc
比如:
int* p = new int[1000000000000];
可能失败。
如果你不想让它抛异常,可以写:
int* p = new(nothrow) int[1000000000000];
if (p == nullptr) {
cout << "申请失败" << endl;
}
需要头文件:
#include <new>
完整写法:
#include <iostream>
#include <new>
using namespace std;
int main() {
int* p = new(nothrow) int[1000000000000];
if (p == nullptr) {
cout << "申请失败" << endl;
} else {
delete[] p;
}
return 0;
}
不过你现在初学 C++,先记住普通 new 就够了。
12.和类的构造函数结合
例如:
class Student {
private:
string name;
int age;
public:
Student(string n, int a) : name(n), age(a) {}
void show() {
cout << name << " " << age << endl;
}
};
普通对象写法:
Student s("Tom", 18);
s.show();
动态对象写法:
Student* p = new Student("Tom", 18);
p->show();
delete p;
这里:
p->show();
等价于:
(*p).show();
因为 p 是指针,所以访问成员函数要用 ->。
13.现代 C++ 不推荐乱用裸 new/delete
现在更推荐用:
vector
string
unique_ptr
shared_ptr
比如动态数组:
以前:
int* a = new int[n];
delete[] a;
现在更推荐:
vector<int> a(n);
对象指针:
以前:
Student* p = new Student;
delete p;
现在更推荐:
auto p = make_unique<Student>();