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

目录

一、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原则应用

十、总结

十一、参考资料


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

一、new 和 delete 的基本概念

1.1 静态内存与动态内存

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

  • 静态内存:用于存储全局变量、静态变量和函数调用栈中的局部变量。这些变量的生命周期在编译时就已经确定,它们的内存分配和释放由编译器自动处理。例如:
#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;
}

 

  • 动态内存:也称为堆内存,程序可以在运行时从堆上分配和释放内存。这种内存分配方式更加灵活,但需要程序员手动管理。new 和 delete 表达式就是用于管理动态内存的工具。

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 有两种形式:为单个对象分配内存和为对象数组分配内存。

①为单个对象分配内存

ClassName* pointer = new ClassName(args);

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

②为对象数组分配内存

ClassName* array = new ClassName[size];

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

1.3 delete 操作符

1.3.1 工作原理

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

②内存释放:

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

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

1.3.2 delete 操作符的用法

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

①释放单个对象的内存

delete pointer;

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

②释放对象数组的内存

delete[] array;

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

二、new 和 delete 的基本使用

2.1 分配和释放单个对象

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

#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 分配和释放数组

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

#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 初始化单个对象

#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 初始化数组

#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。

 四、new 和 delete 与类和对象

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

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

#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 动态创建和销毁对象数组 

#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[] 销毁对象数组时,会为每个对象调用析构函数。

五、new 和 delete 的异常处理

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

#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 异常,并输出错误信息。 

六、new 和 delete 的替代方案:智能指针

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

6.1 std::unique_ptr

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

#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 时,对象会自动销毁。

#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函数

内存分配底层实现:

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:

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/deletemalloc/free
类型安全
构造/析构调用自动手动
内存计算自动手动
异常处理抛出异常返回NULL
重载方式类/全局作用域全局函数
数组处理专用语法需要手动计算

②混用危险示例

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

八、常见错误和注意事项

8.1 内存泄漏

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

void leaky_function() {
    int* p = new int[100];
    // 忘记delete[] p;
}

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

解决方法:使用智能指针

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

8.2 悬空指针

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

#include <iostream>

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

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

8.3 多次释放内存

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

#include <iostream>

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

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

、最佳实践指南

9.1 优先使用智能指针

#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原则应用

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++ 教程,配有丰富的示例代码和清晰的解释,适合初学者学习和理解相关知识。

评论 28
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

byte轻骑兵

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

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

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

打赏作者

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

抵扣说明:

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

余额充值