概览
- 指针
- 迭代器与普通指针有什么区别
- C++的智能指针及其原理
- 悬挂指针和野指针有什么区别?
- 指针常量和常量指针的区别
- 指针和引用有什么区别呢?
- 如何避免悬挂指针?
指针的基础概念
指针是存储内存地址的变量。它指向一个特定类型的对象或变量,并且通过解引用操作(*)可以访问该地址上的值。
示例
:
int a = 10;
int* p = &a; // p是指向a的指针
std::cout << *p; // 解引用p,输出a的值:10
指针的用途很多,例如动态内存分配、函数参数传递、数组操作等。
指针常量与常量指针
- 指针常量(constant pointer):指针本身是常量,指向的地址不能改变,但指向的内容可以改变。
- 常量指针(pointer to constant):指针指向的对象是常量,不能通过指针修改该对象的值,但可以改变指针指向的地址。
int a = 5;
int b = 10;
// 指针常量:指向的地址不能改变
int* const ptr1 = &a;
*ptr1 = 20; // 可以修改a的值
// ptr1 = &b; // 错误,ptr1不能改变指向
// 常量指针:指向的值不能改变
const int* ptr2 = &a;
ptr2 = &b; // 可以改变ptr2的指向
// *ptr2 = 30; // 错误,不能修改指向对象的值
指针和引用的区别
- 指针可以为空,也可以重新指向其他对象;指针的大小是固定的。
- 引用必须在定义时初始化,且不能重新绑定到其他对象。引用实际上是对象的别名,不占用额外内存。
主要区别:指针更灵活,允许指向不同的对象;引用更简单,用于定义后不会变化的别名。
int a = 5;
int* ptr = &a; // 指针
int& ref = a; // 引用
ref = 10; // 通过引用修改a的值
ptr = nullptr; // 指针可以为空
// ref = &b; // 引用不能重新绑定
nullptr 的含义是什么?
nullptr 是C++11引入的,用来统一表示“空指针”概念。它替代了之前使用的 NULL 宏定义。
- 类型安全: 与旧的 NULL 不同,nullptr 有明确的类型,是 std::nullptr_t 类型的常量值,它可以自动转换为任何指针类型,但它不是整数(这避免了 NULL 被解释为整数的歧义)。nullptr 只能用于指针类型,不能用于非指针类型的场景。
- 表示空指针: nullptr 代表一个明确的“空指针”,即它不指向任何有效的内存地址。
指针的基本注意事项
- 解引用空指针:在解引用指针之前,确保它指向有效的内存,否则会导致运行时错误(如段错误)。
- 初始化指针:指针必须初始化(如nullptr或指向有效内存),否则可能成为悬挂指针或野指针。
- 动态内存管理:在使用new分配的内存时,必须记得用delete释放,防止内存泄漏。
悬挂指针与野指针
- 悬挂指针(dangling pointer):指向已经被释放或销毁的内存地址。
- 野指针(wild pointer):未初始化的指针,指向随机的内存位置。
int* ptr = new int(5);
delete ptr; // 释放内存
// 此时ptr是悬挂指针,因为它指向的内存已经释放
ptr = nullptr; // 通过将指针置为nullptr避免悬挂指针
如何避免悬挂指针?
- 使用智能指针:智能指针自动管理内存,避免悬挂指针。
- 设置指针为nullptr:在释放内存后,将指针置为nullptr,避免误用。
- 尽量减少使用原始指针:优先使用智能指针和引用,减少直接使用原始指针的场景。
- 注意作用域:确保指针的作用域与其指向的对象保持一致,避免指向已经销毁的对象。
int* ptr = new int(10);
delete ptr;
ptr = nullptr; // 避免悬挂指针
智能指针及其原理
C++11引入了智能指针,它们自动管理内存,避免手动delete。主要有三种类型的智能指针:
- std::unique_ptr:独占所有权,指针不能共享,生命周期结束时自动释放内存。
- std::shared_ptr:允许多个智能指针共享同一个对象,使用引用计数来管理内存。引用计数为0时,自动释放内存。
- std::weak_ptr:与shared_ptr配合使用,不影响引用计数,防止循环引用。
#include <memory>
std::unique_ptr<int> ptr1 = std::make_unique<int>(10);
std::cout << *ptr1 << std::endl; // 输出10
std::shared_ptr<int> ptr2 = std::make_shared<int>(20);
std::shared_ptr<int> ptr3 = ptr2; // 引用计数增加
std::cout << *ptr3 << std::endl; // 输出20
原理:智能指针通过构造函数和析构函数控制指针的生命周期。当智能指针超出作用域或引用计数为0时,自动释放所管理的内存,避免内存泄漏。
unique_ptr
- 独占所有权:某个对象只允许有一个智能指针来管理它,即对象的所有权是唯一的。当该指针被销毁时,所管理的对象会自动被释放。
- 适合使用场景:动态分配的资源不需要共享,或希望确保资源的唯一性,例如RAII模式下的资源管理。
注意事项
- 不能复制:std::unique_ptr的所有权是独占的,不能进行复制操作,但可以通过移动语义转移所有权。
- 适合动态分配对象时,避免忘记释放资源的情况。
#include <iostream>
#include <memory>
class MyClass {
public:
MyClass() { std::cout << "MyClass constructor\n"; }
~MyClass() { std::cout << "MyClass destructor\n"; }
void display() { std::cout << "MyClass::display\n"; }
};
int main() {
std::unique_ptr<MyClass> ptr1 = std::make_unique<MyClass>(); // 创建一个unique_ptr
ptr1->display(); // 通过ptr1调用对象的方法
// 不能复制unique_ptr,但可以移动
std::unique_ptr<MyClass> ptr2 = std::move(ptr1); // 移动所有权
if (ptr1 == nullptr) {
std::cout << "ptr1 is now nullptr\n"; // 移动后,ptr1为空
}
ptr2->display(); // ptr2接管了对象的所有权
// ptr2被销毁时,自动释放MyClass对象
return 0;
}
输出
MyClass constructor
MyClass::display
ptr1 is now nullptr
MyClass::display
MyClass destructor
shared_ptr
- 共享所有权:允许多个智能指针共享一个对象的所有权。当最后一个指针销毁时,所管理的对象才会被释放。
- 适合使用场景:多个对象或函数需要共享同一资源,比如多个模块之间共享的缓存或数据。
注意事项
- 引用计数:std::shared_ptr会维护一个引用计数,当有新的shared_ptr指向对象时,计数增加;当一个shared_ptr销毁时,计数减少。只有当计数为0时,才释放对象。
- 循环引用问题:当两个shared_ptr对象互相引用时,会导致引用计数无法归零,造成内存泄漏。需要搭配std::weak_ptr来避免循环引用。
#include <iostream>
#include <memory>
class MyClass {
public:
MyClass() { std::cout << "MyClass constructor\n"; }
~MyClass() { std::cout << "MyClass destructor\n"; }
void display() { std::cout << "MyClass::display\n"; }
};
int main() {
std::shared_ptr<MyClass> ptr1 = std::make_shared<MyClass>(); // 创建shared_ptr
{
std::shared_ptr<MyClass> ptr2 = ptr1; // 共享所有权
std::cout << "ptr1 use count: " << ptr1.use_count() << '\n'; // 查看引用计数
std::cout << "ptr2 use count: " << ptr2.use_count() << '\n';
ptr2->display(); // 通过ptr2调用对象方法
} // ptr2出了作用域,引用计数减1
std::cout << "ptr1 use count after ptr2 goes out of scope: " << ptr1.use_count() << '\n';
ptr1->display(); // ptr1仍然可以使用
// ptr1被销毁时,自动释放MyClass对象
return 0;
}
输出
MyClass constructor
ptr1 use count: 2
ptr2 use count: 2
MyClass::display
ptr1 use count after ptr2 goes out of scope: 1
MyClass::display
MyClass destructor
weak_ptr
std::shared_ptr使用引用计数机制管理资源,但如果两个shared_ptr对象互相持有对方的引用,会形成循环引用,导致引用计数无法归零,资源永远无法释放。
解决方案
使用std::weak_ptr来打破循环引用。std::weak_ptr不参与引用计数,不会阻止所管理对象的销毁。它可以从shared_ptr构造,但不能直接使用,需要通过lock()方法临时提升为shared_ptr。
#include <iostream>
#include <memory>
class Node;
class Node {
public:
std::shared_ptr<Node> next;
std::weak_ptr<Node> prev; // 使用weak_ptr打破循环引用
~Node() { std::cout << "Node destroyed\n"; }
};
int main() {
auto node1 = std::make_shared<Node>();
auto node2 = std::make_shared<Node>();
node1->next = node2; // node1指向node2
node2->prev = node1; // node2通过weak_ptr指向node1
return 0; // node1和node2出了作用域,自动销毁
}
输出
Node destroyed
Node destroyed