C++基础整理(2)
注:整理一些突然学到的C++知识,随时mark一下
例如:忘记的关键字用法,新关键字,新数据结构
new 关键字和 delete 关键字用法整理
提示:本文为 C++ 中常用的 new 和 delete的用法和举例
一、new 关键字 in C++
在C++中,new 是一个新的操作符,用于分配动态内存(存在堆区,不是栈区),在堆区开辟新的内存空间以分配对象。当使用 new 时,它会调用对象的构造函数来初始化新分配的内存,并返回指向新创建对象的指针( new 的返回结果是一个指针)。注意 new 完一个对象之后必须使用 delete 语句手动释放在堆区的相应对象的内存空间。
引入新关键字 new / delete 的原因:当使用【非基本的数据类型的对象(即自己定义的 class)】时,对象的创建过程会涉及到【构造函数】的调用,而在对象销毁时则又会调用【析构函数】。相比之下,而原C中的 malloc 和 free 作为库函数,其代码已经预先编译完成,因此无法将构造函数和析构函数的新功能应用于它们。因此 malloc 和 free 仅仅负责内存的分配和释放,而不涉及对象生命周期中的构造和析构过程。这也是new / delete 相比 malloc / free 的优势。
new主要有以下使用场景:
1、分配基本数据类型
代码示例:
int* ptr = new int; // 分配一个整数
*ptr = 100; // 设置整数的值为42
delete ptr; // 释放内存
2、分配自定义的类的对象
假设现在有一个名为 MyClass 的类:
代码示例:
class MyClass {
public:
MyClass(int value) : m_value(value) {}
~MyClass() {}
void printValue() const { std::cout << m_value << std::endl; }
private:
int m_value;
};
//
MyClass* obj = new MyClass(10); // 分配一个MyClass对象,并初始化它
obj->printValue(); // 输出10
delete obj; // 释放内存
3、分配数组
可以使用 new 来新开辟一段数组的内存:
代码示例:
int* arr = new int[5]; // 分配一个包含5个整数的数组
for (int i = 0; i < 5; ++i) {
arr[i] = i * 2; // 设置数组元素的值
}
delete[] arr; // 释放数组的内存
二、delete 关键字 in C++
delete用于释放之前通过new操作符在堆上分配的内存。当你使用new创建一个对象时,该对象会一直占用内存,直到你使用delete释放它。
1、delete 的语法
delete有两种形式:
delete ptr;//用于释放单个对象或基本数据类型的内存。
delete[] arr;//用于释放通过new[]创建的数组的内存。
2、delete 和 delete[] 的行为差异
delete:用于单个对象,它只会调用该对象的析构函数一次,并释放该对象所占用的内存。
delete[]:用于数组,它会对数组中的每个元素调用析构函数,并释放整个数组所占用的内存。(指针数组例外,要特殊处理)
3、指针数组的特殊处理
指针数组是一个很多指针构成的数组,其元素是指针而非对象。当需要释放这样的数组时,需要先确保每个指针所指向的对象(如果有的话)都已经被正确地析构掉了,然后再释放指针数组本身的内存。
下面是一个【指针数组】配合 delete 的例子:
class MyClass {
public:
MyClass(int value) : m_value(value) {
std::cout << "MyClass(" << value << ") constructed\n";
}
~MyClass() {
std::cout << "MyClass(" << m_value << ") destructed\n";
}
void printValue() const {
std::cout << "Value: " << m_value << std::endl;
}
private:
int m_value;
};
int main() {
// 创建一个包含3个MyClass指针的数组
MyClass** ptrArray = new MyClass*[3];
// 为每个指针分配一个MyClass对象,并初始化
for (int i = 0; i < 3; ++i) {
ptrArray[i] = new MyClass(i);
}
// 使用对象...
for (int i = 0; i < 3; ++i) {
ptrArray[i]->printValue();
}
// 释放每个MyClass对象
for (int i = 0; i < 3; ++i) {
delete ptrArray[i]; // 调用每个对象的析构函数
ptrArray[i] = nullptr; // 避免悬挂指针
}
// 释放指针数组本身的内存
delete[] ptrArray; // 不需要调用析构函数,因为ptrArray是指针的数组 ,这里绝对不能使用delete ptrArray,会报错
ptrArray = nullptr; // 避免悬挂指针
return 0;
}
在这个例子中,我们首先创建了一个包含3个MyClass*类型元素的指针数组ptrArray。然后,我们为每个指针分配了一个MyClass对象,并初始化了它们。在释放内存之前,我们遍历了数组,并使用delete来释放每个MyClass对象(调用它们的析构函数)。最后,我们使用delete[]来释放指针数组ptrArray本身的内存。注意,因为ptrArray是一个指针数组,而不是对象数组,所以delete[]不会调用指针的析构函数(指针没有析构函数)。它只负责释放数组占用的内存。简单的说,delete 和 delete[] 不能互用。delete 删除/释放某个指针,而delete[] 删除/释放某个数组。
4、一些问题和风险
(1)悬挂指针:使用delete释放内存后,最好将指针设置为nullptr。这样做可以防止出现悬挂指针(dangling pointer),即指向【已被释放的内存空间】的指针。尝尝试访问悬挂指针的代码将导致未定义行为,通常导致程序崩溃。
(2)内存泄漏:不释放这些 new 出来的内存可能会导致内存泄漏,这是一种常见的编程错误,它可能导致程序消耗越来越多的内存,最终耗尽系统资源,必须避免。
(3)必须配对使用new 和 delete,以及 new[] 和 delete[] 。混用它们(例如,用delete释放通过new[]分配的内存)也会导致未定义行为。
(4)当使用delete释放内存时,对象的析构函数将被自动调用。如果你的类有管理其他资源(如动态分配的内存、文件句柄等),确保在析构函数中释放这些资源。
(5)异常安全:在构造函数可能抛出异常的情况下,使用 new 可能会导致资源泄漏或其他问题。
(6)智能指针:在现代C++编程中,通常推荐使用智能指针(如unique_ptr和shared_ptr,后续章节会讲解)来自动管理动态分配的内存。因为它们可以自动处理内存释放,减少内存泄漏和悬挂指针的风险。虽然 new 和 delete 提供了手动管理内存的能力,但在许多情况下,使用智能指针和RAII(资源获取即初始化)技术来自动管理资源更为安全和方便。
5、类成员函数出现=delete的情况
如果一个类的成员函数后面紧跟着= delete,那么这意味着这个函数被显式地删除了,也就是说,禁止调用该函数。
= delete 通常用于以下情况:
禁止类的拷贝构造或移动构造:如果不希望一个类被拷贝或移动,可以将其拷贝构造函数和移动构造函数后面加一句= delete。
代码示例
class NonCopyable {
public:
NonCopyable(const NonCopyable&) = delete;
NonCopyable& operator=(const NonCopyable&) = delete;
};
成员函数的“封印”:如果有一个成员函数,但在某个特定的类实现中,你不希望它被调用,可以将其设为= delete的。
代码示例
class MyClass {
public:
void MyFunction() = delete;
};
有时,仅仅将成员函数设为私有的 private 并不足以明确表示不希望它被调用。使用= delete可以更加明确地表达意图。使用= delete的好处是,它会在编译时捕获到任何尝试调用该函数的代码的行为,从而避免了运行时的错误。