目录
一、什么是原始指针(*、&)、什么是动态分配和释放内存(malloc和free、new和delete)
二、智能指针(shared_ptr、unique_ptr)和原始指针区别、如何使用
2.1智能指针(shared_ptr、unique_ptr)和原始指针(*)区别
一、什么是原始指针(*、&)、什么是动态分配和释放内存(malloc和free、new和delete)
原始指针是指直接操作内存地址的指针,通常用 * 和 & 运算符来读取和赋值内存地址。它们是 C 和 C++ 中的基本概念,用于进行低级别的内存管理和操作。
1.1无动态分配内存的使用方法
在函数内部定义的指针 MyClass* ptr; 如果没有使用 new或malloc 来创建对象,而是指向了一个栈上的对象或者是全局对象,那么当函数结束时,这个指针所指向的对象会自动被销毁,无需手动释放。示例代码如下:
void nonDynamicPointerExample() {
MyClass obj; // 在栈上创建对象
MyClass* ptr = &obj; // 指针指向栈上的对象,不需要手动释放
}
int main() {
nonDynamicPointerExample(); // 调用示例函数
return 0;
}
1.2动态分配内存使用方法
new 和 delete 是 C++ 中的关键字,用于动态分配和释放内存。它们可以调用对象的构造函数和析构函数,提供了更高级的内存管理功能。与之相对应的是 C 语言中的 malloc 和 free 函数,用于动态分配和释放内存。
int* ptr = new int; // 使用 new 分配整数的内存空间
*ptr = 42; // 在分配的内存中存储数据
delete ptr; // 使用 delete 释放内存
int* ptr = (int*)malloc(sizeof(int)); // 使用 malloc 分配整数的内存空间
*ptr = 42; // 在分配的内存中存储数据
free(ptr); // 使用 free 释放内存
1.3为什么要动态分配内存?
在函数内部使用 new 动态分配的局部变量相对于直接创建栈上的局部变量(自动变量)有一些特定的优点和用途:
a.生命周期控制:通过 new 创建的局部变量的生命周期不再局限于函数的执行范围。它们可以在函数结束后继续存在,直到显式调用 delete 进行释放为止。这使得你可以在函数外部继续使用这些对象,或者将它们传递给其他函数,而不会导致对象的销毁。
b.内存空间灵活性:使用 new 创建的局部变量可以分配比栈上局部变量更大的内存空间,因为栈空间通常是有限的。这对于需要大量内存或者动态增长的数据结构(如动态数组)非常有用。
c.对象持久性:有时候需要在函数调用之间保持对象的状态或者数据。通过 new 创建的局部变量可以在函数调用之间保持对象的状态,而不会因为函数结束而销毁。
下 面是一个示例,展示了如何在函数内部使用 new 创建对象,并将对象的指针传递到函数外部进行使用:
#include <iostream>
class MyClass {
public:
MyClass(int val) : value(val) {}
void printValue() { std::cout << "Value: " << value << std::endl; }
private:
int value;
};
MyClass* createObject() {
MyClass* ptr = new MyClass(42);
return ptr;
}
int main() {
MyClass* objPtr = createObject(); // 在函数内部创建对象,并返回指针
objPtr->printValue(); // 在函数外部使用对象指针调用成员函数
delete objPtr; // 释放内存
return 0;
}
在这个示例中,createObject 函数内部使用 new 创建了一个 MyClass 对象,并返回了这个对象的指针给调用函数 main。调用函数 main 可以通过这个指针访问并操作这个对象,直到需要释放内存时调用 delete。
- malloc 和 new有什么区别
malloc 和 new 是用于在 C 和 C++ 中动态分配内存的关键字,它们之间有几个重要的区别:
a.用法和语法:
- malloc 是 C 语言中的函数,用于动态分配内存。它的原型是 void* malloc(size_t size),返回一个指向分配内存的指针。
- new 是 C++ 中的关键字,用于动态分配内存并构造对象。它有多种形式,例如 new、new[]、new (placement) 等,用于不同的内存分配和构造对象的场景。
b.类型安全:
- malloc 返回的是 void* 类型的指针,需要进行类型转换后才能使用,容易出现类型错误。
- new 在分配内存时可以直接指定所需对象的类型,它会自动进行类型转换并返回相应类型的指针,因此更加类型安全。
c.构造函数调用: - malloc 分配的内存只是一块原始的内存空间,并不会调用对象的构造函数。如果需要构造对象,需要手动调用构造函数。 - new 在分配内存后会自动调用对象的构造函数来初始化对象,因此更方便且符合面向对象的设计理念。
d.内存管理:
- malloc 分配的内存需要手动调用 free 函数来释放,否则可能会造成内存泄漏。
- new 分配的内存会在对象生命周期结束时自动调用对象的析构函数,并释放相关内存,不需要手动释放。在 C++ 中,当对象的生命周期结束时(例如对象超出作用域、delete 对象指针等),会自动调用对象的析构函数。但这并不是由 new 自动调用析构函数并释放内存,而是由 C++ 的对象生命周期管理机制来实现的。
e.数组分配:
- malloc 和 free 通常用于分配和释放动态数组的内存。
- new[] 和 delete[] 是 C++ 中专门用于分配和释放动态数组的关键字,它们可以正确处理数组元素的构造和析构。
总的来说,new 更适用于 C++ 中的对象管理和构造,提供了更多的便利和类型安全;而 malloc 则更适合于 C 语言中的内存分配和释放,不涉及对象的构造和析构。在 C++ 中推荐使用 new 和 delete,在 C 语言中使用 malloc 和 free。
二、智能指针(shared_ptr、unique_ptr)和原始指针区别、如何使用
2.1智能指针(shared_ptr、unique_ptr)和原始指针(*)区别
a.资源管理:
- 非智能指针:例如裸指针 T* ptr = new T;,需要手动管理资源的生命周期,包括手动释放内存(delete ptr;)和处理异常情况等。如果忘记释放内存或者处理异常,可能会导致内存泄漏或者程序崩溃。
- 智能指针:例如 std::shared_ptr<T> ptr = std::make_shared<T>();,智能指针会自动管理资源的生命周期,当没有指针指向资源时会自动释放资源,避免了内存泄漏和手动管理资源的繁琐工作。
b.所有权管理:
- 非智能指针:所有权需要手动管理,如果多个地方都需要访问同一个资源,需要自行确保正确地传递和释放所有权。
- 智能指针:智能指针可以自动管理所有权,例如 std::shared_ptr 允许多个指针共享同一个资源,通过引用计数来管理所有权,而 std::unique_ptr 则是独占所有权的智能指针。
下面是一个简单的实例,展示了智能指针和非智能指针的区别:
#include <iostream>
#include <memory>
class MyClass {
public:
MyClass() { std::cout << "MyClass constructor\n"; }
~MyClass() { std::cout << "MyClass destructor\n"; }
};
void nonSmartPointerExample() {
MyClass* ptr = new MyClass(); // 非智能指针,需要手动释放资源
delete ptr; // 手动释放内存
}
void smartPointerExample() {
std::unique_ptr<MyClass> ptr = std::make_unique<MyClass>(); // 智能指针,自动管理资源生命周期
}
void smartPointerExample2() {
std::shared_ptr<MyClass> ptr = std::make_shared<MyClass>(42); // 智能指针,自动管理资源生命周期
}
int main() {
nonSmartPointerExample(); // 非智能指针的例子
smartPointerExample(); // 智能指针的例子
smartPointerExample2(); // 智能指针的例子
return 0;
}
在上面的例子中,nonSmartPointerExample 使用了非智能指针 MyClass* ptr,需要手动释放内存;而smartPointerExample 使用了智能指针 std::unique_ptr,资源的管理由智能指针自动完成,不需要手动释放。
2.2shared_ptr、unique_ptr的区别
std::shared_ptr 和 std::unique_ptr 是 C++ 中用于管理动态分配的资源(如内存)的智能指针。它们之间的区别主要体现在所有权和生命周期管理上:
a.std::shared_ptr:
- 允许多个指针共享同一个对象,通过引用计数来管理对象的生命周期。
- 当最后一个指向对象的 shared_ptr 被销毁时,对象才会被销毁,从而可以实现多个地方共享同一个资源。
- 可以通过 std::make_shared 或 std::shared_ptr 的构造函数来创建。
b.std::unique_ptr:
- 独占所有权,不能拷贝或者赋值给其他 unique_ptr。
- 当指向对象的 unique_ptr 被销毁时,对象也会被销毁,可以确保资源的独占性和生命周期管理。
- 适合于单一所有权的情况,比如只有一个地方需要拥有和管理资源。
综上所述,区别在于对资源所有权和生命周期管理的不同需求。std::shared_ptr 适用于多个地方共享同一个资源的情况,而 std::unique_ptr 适用于单一所有权的情况。 当使用std::unique_ptr 时,需要注意其独占所有权的特性。
下面是一个使用 std::unique_ptr 的实际代码示例:
#include <iostream>
#include <memory> // 包含智能指针头文件
class MyClass {
public:
MyClass(int val) : value(val) {}
void printValue() { std::cout << "Value: " << value << std::endl; }
private:
int value;
};
std::unique_ptr<MyClass> createObject() {
// 使用std::make_unique创建unique_ptr
std::unique_ptr<MyClass> ptr = std::make_unique<MyClass>(42);
return ptr;
}
int main() {
std::unique_ptr<MyClass> objPtr1 = createObject(); // 在函数内部创建unique_ptr对象
objPtr1->printValue(); // 在函数外部使用对象指针调用成员函数
std::unique_ptr<MyClass> objPtr2 = std::move(objPtr1); // 转移资源所有权
// unique_ptr在函数结束时会自动释放所管理的对象,无需手动调用delete
return 0;
}
在这个示例中,createObject 函数返回一个 std::unique_ptr,在函数结束时,objPtr 1的生命周期结束,指向的对象会自动释放。与 std::shared_ptr 不同,std::unique_ptr 确保了独占所有权,同一时间只有一个指针可以指向所管理的对象。需要注意的是,std::unique_ptr 不能直接进行复制操作,因为它禁止了复制构造函数和赋值操作符。如果需要转移资源所有权,可以使用 std::move 来进行移动语义的操作objPtr 2。
2.3智能指针一定比原始指针好吗
当谈及智能指针(例如 std::shared_ptr、std::unique_ptr)和原始指针(如 malloc、new)时,它们之间有几个关键区别以及各自的优缺点和适用场景:
a.智能指针:
- 优点:
- 自动内存管理:智能指针可以自动管理内存,避免了手动释放内存的问题。
- 安全性增强:智能指针提供了类型安全,避免了常见的指针错误。
- 生命周期管理:智能指针在作用域结束时自动释放内存,避免了手动内存管理带来的问题。
- 缺点:
- 额外开销:智能指针可能会有一些额外开销,因为它们管理了额外的元数据。
- 不适用于特定情况:有些情况下,使用智能指针可能不太合适,比如需要对对象进行底层内存管理、需要与 C 接口交互、需要实现自定义的内存管理策略等。
- 适用场景:
- std::shared_ptr:用于共享所有权的情况,允许多个指针共同指向同一对象。
- std::unique_ptr:用于独占所有权的情况,确保只有一个指针拥有对象的所有权。
a.原始指针:
- 优点:
- 灵活性:原始指针提供了更多的灵活性,可以用于更底层的内存管理。
- 不带开销:原始指针没有智能指针的额外开销。
- 缺点:
- 容易出错:容易出现内存泄漏、悬挂指针和释放非法指针等问题。
- 手动管理:需要手动释放内存,容易导致内存泄漏或内存安全问题。
- 适用场景:
- 对于低级别的内存管理、与 C 语言接口交互或需要更高的性能要求时。
总的来说,智能指针提供了更安全和方便的内存管理方式,尤其适用于管理对象的所有权和生命周期。而原始指针则更适合于一些特定的、需要更灵活内存管理的场景。