首页
学习
活动
专区
圈层
工具
发布
社区首页 >专栏 >【C++入门】new 和 delete表达式

【C++入门】new 和 delete表达式

作者头像
byte轻骑兵
发布2026-01-20 17:22:51
发布2026-01-20 17:22:51
950
举报

在C++编程中,动态内存管理是必须掌握的核心技能之一。new和delete作为C++提供的动态内存管理操作符,承担着在堆内存中创建和销毁对象的重要职责。与C语言的malloc/free不同,new和delete不仅能完成内存分配,还会自动调用对象的构造函数和析构函数,使得它们成为C++面向对象编程中不可或缺的工具。

一、newdelete 的基本概念

1.1 静态内存与动态内存

在了解 newdelete 之前,我们需要先明确静态内存和动态内存的概念。

  • 静态内存:用于存储全局变量、静态变量和函数调用栈中的局部变量。这些变量的生命周期在编译时就已经确定,它们的内存分配和释放由编译器自动处理。例如:
代码语言:javascript
复制
#include <iostream>

// 全局变量,存储在静态内存中
int globalVar = 10;

void func() {
    // 局部静态变量,存储在静态内存中
    static int staticVar = 20;
    std::cout << "Static variable: " << staticVar << std::endl;
}

int main() {
    // 局部变量,存储在栈上
    int localVar = 30;
    std::cout << "Local variable: " << localVar << std::endl;
    func();
    return 0;
}
  • 动态内存:也称为堆内存,程序可以在运行时从堆上分配和释放内存。这种内存分配方式更加灵活,但需要程序员手动管理。newdelete 表达式就是用于管理动态内存的工具。

1.2 new 操作符

1.2.1 工作原理

①内存分配

  • 当调用 new 操作符时,它首先会调用 C++ 的内存分配函数(通常是 operator new,但用户也可以重载这个函数以提供自定义的内存分配策略)。
  • operator new 会向堆(heap)请求足够的内存来存储指定类型的一个或多个对象。如果内存分配成功,它会返回一个指向该内存的指针;如果失败,则抛出 std::bad_alloc 异常(除非指定了nothrow版本)。

②对象构造

  • 在内存分配成功后,new 操作符接着会调用对象的构造函数来初始化分配的内存。对于单个对象,它直接调用该对象的构造函数;对于对象数组,它调用默认构造函数(无参构造函数)来初始化数组中的每个元素。
  • 如果构造函数抛出异常,则 new 操作符会捕获这个异常,并使用 operator delete 释放之前分配的内存,然后重新抛出异常。

③ 返回指针:一旦对象被成功构造,new 操作符就会返回指向该对象的指针。

1.2.2 new 操作符的用法

new 操作符用于在堆(heap)上分配内存,并可能调用对象的构造函数来初始化内存。new 有两种形式:为单个对象分配内存和为对象数组分配内存。

①为单个对象分配内存

代码语言:javascript
复制
ClassName* pointer = new ClassName(args);

ClassName 是要创建的对象类型,args 是传递给对象构造函数的参数(如果构造函数不需要参数,则省略)。new 操作符首先为 ClassName 类型的对象分配足够的内存,然后调用该类型的构造函数(如果有的话),并返回指向新分配内存的指针。

②为对象数组分配内存

代码语言:javascript
复制
ClassName* array = new ClassName[size];

size 指定了要分配的数组的元素数量。new 操作符为 sizeClassName 类型的对象分配内存,但只调用默认构造函数(无参构造函数)来初始化这些对象。如果没有默认构造函数,则编译失败。

1.3 delete 操作符

1.3.1 工作原理

①对象析构:当调用 deletedelete[] 操作符时,它首先会调用指向对象的指针所指向的对象的析构函数(如果有的话)。对于 delete,它只调用单个对象的析构函数;对于 delete[],它会遍历数组中的每个对象并调用它们的析构函数。

②内存释放:

  • 析构函数执行完毕后,deletedelete[] 操作符会调用 C++ 的内存释放函数(通常是 operator deleteoperator delete[],但同样可以重载)。
  • operator deleteoperator delete[] 会将之前分配的内存归还给堆,以便后续的内存分配请求可以使用。

③指针置空(可选):虽然 deletedelete[] 操作符本身不会将指针置为 nullptr,但这是一个良好的编程实践,以避免野指针的出现。应该在 deletedelete[] 调用后立即将指针设置为 nullptr

1.3.2 delete 操作符的用法

delete 操作符用于释放之前通过 new 分配的内存。与 new 类似,delete 也有两种形式:用于单个对象和对象数组。

①释放单个对象的内存

代码语言:javascript
复制
delete pointer;

pointer 是指向之前通过 new 分配的内存的指针。delete 操作符首先调用对象的析构函数(如果有的话),然后释放与该对象关联的内存。

②释放对象数组的内存

代码语言:javascript
复制
delete[] array;

对于通过 new[] 分配的对象数组,必须使用 delete[] 来释放内存。delete[] 操作符会遍历数组中的每个对象,并调用它们的析构函数(如果有的话),然后释放整个数组的内存。

二、newdelete 的基本使用

2.1 分配和释放单个对象

下面是一个简单的示例,演示如何使用 newdelete 分配和释放单个对象:

代码语言:javascript
复制
#include <iostream>

int main() {
    // 使用 new 分配一个整数对象
    int* ptr = new int;
    *ptr = 10;
    std::cout << "Value: " << *ptr << std::endl;

    // 使用 delete 释放内存
    delete ptr;

    return 0;
}

使用 new 分配了一个整数对象,并将其初始化为 10。然后,使用 delete 释放了该对象所占用的内存。

2.2 分配和释放数组

下面是一个分配和释放数组的示例:

代码语言:javascript
复制
#include <iostream>

int main() {
    // 使用 new 分配一个包含 5 个整数的数组
    int* arr = new int[5];
    for (int i = 0; i < 5; i++) {
        arr[i] = i;
    }

    // 输出数组元素
    for (int i = 0; i < 5; i++) {
        std::cout << arr[i] << " ";
    }
    std::cout << std::endl;

    // 使用 delete[] 释放数组内存
    delete[] arr;

    return 0;
}

使用 new 分配了一个包含 5 个整数的数组,并对其进行初始化。然后,使用 delete[] 释放了该数组所占用的内存。

new 表达式的初始化

new 表达式可以在分配内存的同时对对象进行初始化。

3.1 初始化单个对象

代码语言:javascript
复制
#include <iostream>

int main() {
    // 直接初始化
    int* ptr1 = new int(10);
    std::cout << "Value of ptr1: " << *ptr1 << std::endl;

    // 值初始化
    int* ptr2 = new int();
    std::cout << "Value of ptr2: " << *ptr2 << std::endl;

    delete ptr1;
    delete ptr2;

    return 0;
}

ptr1 使用直接初始化的方式将分配的整数对象初始化为 10,ptr2 使用值初始化的方式将分配的整数对象初始化为 0。

3.2 初始化数组

代码语言:javascript
复制
#include <iostream>

int main() {
    // 初始化数组
    int* arr1 = new int[5]{1, 2, 3, 4, 5};
    for (int i = 0; i < 5; i++) {
        std::cout << arr1[i] << " ";
    }
    std::cout << std::endl;

    // 值初始化数组
    int* arr2 = new int[5]();
    for (int i = 0; i < 5; i++) {
        std::cout << arr2[i] << " ";
    }
    std::cout << std::endl;

    delete[] arr1;
    delete[] arr2;

    return 0;
}

arr1 使用初始化列表对数组元素进行初始化,arr2 使用值初始化的方式将数组元素初始化为 0。

四、newdelete 与类和对象

newdelete 也可以用于动态创建和销毁类的对象。

4.1 动态创建和销毁单个对象

代码语言:javascript
复制
#include <iostream>

class MyClass {
public:
    MyClass() {
        std::cout << "Constructor called" << std::endl;
    }
    ~MyClass() {
        std::cout << "Destructor called" << std::endl;
    }
    void print() {
        std::cout << "Hello, World!" << std::endl;
    }
};

int main() {
    // 使用 new 创建对象
    MyClass* obj = new MyClass;
    obj->print();

    // 使用 delete 销毁对象
    delete obj;

    return 0;
}

使用 new 创建 MyClass 对象时,会调用类的构造函数;使用 delete 销毁对象时,会调用类的析构函数。

4.2 动态创建和销毁对象数组

代码语言:javascript
复制
#include <iostream>

class MyClass {
public:
    MyClass() {
        std::cout << "Constructor called" << std::endl;
    }
    ~MyClass() {
        std::cout << "Destructor called" << std::endl;
    }
};

int main() {
    // 使用 new 创建对象数组
    MyClass* arr = new MyClass[3];

    // 使用 delete[] 销毁对象数组
    delete[] arr;

    return 0;
}

使用 new 创建对象数组时,会为每个对象调用构造函数;使用 delete[] 销毁对象数组时,会为每个对象调用析构函数。

五、newdelete 的异常处理

new 表达式无法分配所需的内存时,会抛出 std::bad_alloc 异常。为了避免程序崩溃,可以使用异常处理机制来捕获并处理该异常。

代码语言:javascript
复制
#include <iostream>
#include <new>

int main() {
    try {
        // 尝试分配大量内存
        int* ptr = new int[1000000000];
        delete[] ptr;
    } catch (const std::bad_alloc& e) {
        std::cerr << "Memory allocation failed: " << e.what() << std::endl;
    }

    return 0;
}

使用 try-catch 块捕获 std::bad_alloc 异常,并输出错误信息。

六、newdelete 的替代方案:智能指针

虽然 newdelete 提供了动态内存管理的基本功能,但手动管理内存容易出现内存泄漏和悬空指针等问题。C++ 标准库提供了智能指针来自动管理动态内存,避免这些问题。

6.1 std::unique_ptr

std::unique_ptr 是一种独占式智能指针,它确保同一时间只有一个指针可以指向该对象。当 std::unique_ptr 离开作用域时,会自动释放所指向的对象。

代码语言:javascript
复制
#include <iostream>
#include <memory>

class MyClass {
public:
    MyClass() {
        std::cout << "Constructor called" << std::endl;
    }
    ~MyClass() {
        std::cout << "Destructor called" << std::endl;
    }
    void print() {
        std::cout << "Hello, World!" << std::endl;
    }
};

int main() {
    // 创建 std::unique_ptr
    std::unique_ptr<MyClass> ptr = std::make_unique<MyClass>();
    ptr->print();

    // 当 ptr 离开作用域时,对象会自动销毁
    return 0;
}

6.2 std::shared_ptr

std::shared_ptr 是一种共享式智能指针,多个 std::shared_ptr 可以指向同一个对象,并且会维护一个引用计数。当引用计数为 0 时,对象会自动销毁。

代码语言:javascript
复制
#include <iostream>
#include <memory>

class MyClass {
public:
    MyClass() {
        std::cout << "Constructor called" << std::endl;
    }
    ~MyClass() {
        std::cout << "Destructor called" << std::endl;
    }
    void print() {
        std::cout << "Hello, World!" << std::endl;
    }
};

int main() {
    // 创建 std::shared_ptr
    std::shared_ptr<MyClass> ptr1 = std::make_shared<MyClass>();
    std::shared_ptr<MyClass> ptr2 = ptr1;

    ptr1->print();
    ptr2->print();

    // 当 ptr1 和 ptr2 都离开作用域时,对象会自动销毁
    return 0;
}

七、底层机制剖析

7.1 operator new/delete函数

内存分配底层实现:

代码语言:javascript
复制
void* operator new(size_t size) {
    if (void* mem = malloc(size))
        return mem;
    else
        throw std::bad_alloc();
}

void operator delete(void* mem) noexcept {
    free(mem);
}

7.2 自定义内存管理

重载类专属operator new:

代码语言:javascript
复制
class MyClass {
public:
    static void* operator new(size_t size) {
        std::cout << "Custom new for MyClass\n";
        return ::operator new(size);
    }
    
    static void operator delete(void* p) {
        std::cout << "Custom delete for MyClass\n";
        ::operator delete(p);
    }
};

7.3 与malloc/free的对比

①关键区别

特性

new/delete

malloc/free

类型安全

构造/析构调用

自动

手动

内存计算

自动

手动

异常处理

抛出异常

返回NULL

重载方式

类/全局作用域

全局函数

数组处理

专用语法

需要手动计算

②混用危险示例

代码语言:javascript
复制
// 危险操作!
MyClass* p = (MyClass*)malloc(sizeof(MyClass));
p->MyClass();  // 手动调用构造函数(非常规操作)
// ...使用对象...
p->~MyClass(); // 手动调用析构函数
free(p);

八、常见错误和注意事项

8.1 内存泄漏

如果使用 new 分配了内存,但没有使用 delete 释放,就会导致内存泄漏。例如:

代码语言:javascript
复制
void leaky_function() {
    int* p = new int[100];
    // 忘记delete[] p;
}

为了避免内存泄漏,应该始终确保在不再需要内存时使用 delete 释放它,或者使用智能指针来自动管理内存。

解决方法:使用智能指针

代码语言:javascript
复制
#include <memory>
void safe_function() {
    auto p = std::make_unique<int[]>(100);
    // 自动释放内存
}

8.2 悬空指针

当使用 delete 释放内存后,指向该内存的指针就会变成悬空指针。如果继续使用悬空指针,会导致未定义行为。例如:

代码语言:javascript
复制
#include <iostream>

int main() {
    int* ptr = new int;
    *ptr = 10;
    delete ptr;
    // 使用悬空指针
    std::cout << *ptr << std::endl;
    return 0;
}

为了避免悬空指针,在释放内存后,应该将指针置为 nullptr

8.3 多次释放内存

如果对同一块内存多次使用 delete 释放,会导致未定义行为。例如:

代码语言:javascript
复制
#include <iostream>

int main() {
    int* ptr = new int;
    delete ptr;
    // 多次释放内存
    delete ptr;
    return 0;
}

为了避免多次释放内存,应该确保每个 new 只对应一个 delete,并且在释放内存后将指针置为 nullptr

、最佳实践指南

9.1 优先使用智能指针

代码语言:javascript
复制
#include <memory>

// 独占所有权
std::unique_ptr<MyClass> uptr(new MyClass());

// 共享所有权
std::shared_ptr<MyClass> sptr = std::make_shared<MyClass>();

// 数组支持(C++17)
auto arrPtr = std::make_unique<int[]>(10);

9.2 RAII原则应用

代码语言:javascript
复制
class ResourceHolder {
    int* resource;
public:
    ResourceHolder(size_t size) : resource(new int[size]) {}
    ~ResourceHolder() { delete[] resource; }
    // 禁用拷贝(C++11)
    ResourceHolder(const ResourceHolder&) = delete;
    ResourceHolder& operator=(const ResourceHolder&) = delete;
    // 移动语义支持(C++11)
    ResourceHolder(ResourceHolder&& other) : resource(other.resource) {
        other.resource = nullptr;
    }
};

十、总结

正确使用new和delete是C++开发者的基本功,需要注意:

  • 严格配对使用new/delete和new[]/delete[]
  • 优先使用智能指针管理资源
  • 对于大块内存分配要考虑异常安全
  • 理解底层内存管理机制
  • 遵循RAII原则设计资源管理类

现代C++(C++11及后续标准)提供了更安全的内存管理工具,建议在实际开发中优先使用智能指针和标准容器,尽量减少直接使用new/delete的需要。只有深入理解底层机制,才能更好地使用高层抽象工具。


十一、参考资料

  • 《C++ Primer(第 5 版)》这本书是 C++ 领域的经典之作,对 C++ 的基础语法和高级特性都有深入讲解。
  • 《Effective C++(第 3 版)》书中包含了很多 C++ 编程的实用建议和最佳实践。
  • 《C++ Templates: The Complete Guide(第 2 版)》该书聚焦于 C++ 模板编程,而using声明在模板编程中有着重要应用,如定义模板类型别名等。
  • C++ 官方标准文档:C++ 标准文档是最权威的参考资料,可以查阅最新的 C++ 标准(如 C++11、C++14、C++17、C++20 等)文档。例如,ISO/IEC 14882:2020 是 C++20 标准的文档,可从相关渠道获取其详细内容。
  • cppreference.com:这是一个非常全面的 C++ 在线参考网站,提供了详细的 C++ 语言和标准库文档。
  • LearnCpp.com:该网站提供了系统的 C++ 教程,配有丰富的示例代码和清晰的解释,适合初学者学习和理解相关知识。

本文参与 腾讯云自媒体同步曝光计划,分享自作者个人站点/博客。
原始发表:2025-03-17,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 一、new 和 delete 的基本概念
    • 1.1 静态内存与动态内存
    • 1.2 new 操作符
      • 1.2.1 工作原理
      • 1.2.2 new 操作符的用法
    • 1.3 delete 操作符
      • 1.3.1 工作原理
      • 1.3.2 delete 操作符的用法
  • 二、new 和 delete 的基本使用
    • 2.1 分配和释放单个对象
    • 2.2 分配和释放数组
  • 三、new 表达式的初始化
    • 3.1 初始化单个对象
    • 3.2 初始化数组
  • 四、new 和 delete 与类和对象
    • 4.1 动态创建和销毁单个对象
    • 4.2 动态创建和销毁对象数组
  • 五、new 和 delete 的异常处理
  • 六、new 和 delete 的替代方案:智能指针
    • 6.1 std::unique_ptr
    • 6.2 std::shared_ptr
  • 七、底层机制剖析
    • 7.1 operator new/delete函数
    • 7.2 自定义内存管理
    • 7.3 与malloc/free的对比
  • 八、常见错误和注意事项
    • 8.1 内存泄漏
    • 8.2 悬空指针
    • 8.3 多次释放内存
  • 九、最佳实践指南
    • 9.1 优先使用智能指针
    • 9.2 RAII原则应用
  • 十、总结
  • 十一、参考资料
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档