C++ 指针及内存管理
1. 指针的基本概念
-
指针定义:指针是一个变量,用于存储另一个变量的内存地址。
-
基本语法:
-
int* ptr; // 声明一个指向整数的指针
-
指针初始化:指针必须指向有效的内存地址。int a = 10;
-
int* ptr = &a; // ptr 指向 a 的地址
-
指针解引用:使用
*
操作符可以访问指针指向的值。 -
std::cout << *ptr; // 输出 10
2. 指针的使用
-
访问变量:
-
int a = 5;
int* ptr = &a; // ptr 指向 a
*ptr = 10; // 修改 a 的值为 10
std::cout << a; // 输出 10 -
指针运算:
- 指针可以进行加减操作,用于遍历数组。
- int arr[] = {1, 2, 3};
int* p = arr; // p 指向 arr 的首元素
std::cout << *(p + 1); // 输出 2
3. 指针作为函数参数
- 通过指针传递参数:可以修改调用者提供的数据。
-
void increment(int* ptr) {
(*ptr)++; // 修改指针指向的值
}int main() {
int value = 5;
increment(&value); // 传递 value 的地址
std::cout << value; // 输出 6
}
4. const
修饰指针
-
const int* ptr
(指向常量的指针)这意味着指针指向的数据是常量,不能通过该指针修改指向的值,但指针本身是可以改变指向其他地址的。
-
int a = 5, b = 10;
const int* ptr = &a; // ptr 指向 a,不能通过 ptr 修改 a 的值
ptr = &b; // 合法,指针可以指向 b,但仍不能通过 ptr 修改 b 的值
//*ptr = 20; // 错误,不能通过 ptr 修改 *ptr 的值 -
int* const ptr
(常量指针)这里的含义是,指针本身是常量,指针一旦指向某个地址,不能再改变它指向其他地址,但可以修改指向的值。
-
int a = 5, b = 10;
int* const ptr = &a; // ptr 是常量指针,不能指向其他地址
*ptr = 20; // 合法,可以修改 a 的值为 20
//ptr = &b; // 错误,不能修改 ptr 指向的地址 -
const int* const ptr
(指向常量的常量指针)这里既不能修改指针指向的地址,也不能修改指针指向的数据。这是最严格的一种声明。
-
int a = 5, b = 10;
const int* const ptr = &a; // ptr 是常量指针,指向的值也是常量
//*ptr = 20; // 错误,不能通过 ptr 修改 a 的值
//ptr = &b; // 错误,不能修改 ptr 指向的地址
5. void
关键字
-
定义:表示没有类型,常用于函数的返回类型。
-
示例:
-
void doNothing() {
// 什么也不做
} -
指向
void
的指针:可以指向任何类型,但需转换。 -
void* ptr;
int a = 5;
ptr = &a; // ptr 指向整型数据
int* intPtr = (int*)ptr; // 强制转换
std::cout << *intPtr; // 输出 5
6. C++ 内存模型
C++ 内存主要分为以下几个区域:
6.1 栈(Stack)
- 管理方式:后进先出(LIFO),自动分配和释放。
- 用途:存储局部变量和函数调用的参数。
- 特点:
- 分配速度快。
- 存储大小有限,超出可能导致栈溢出。
- void function() {
int localVar = 5; // 存储在栈中
} // localVar 在函数返回时自动释放
6.2 堆(Heap)
- 管理方式:手动分配和释放。
- 用途:动态内存分配,用于不确定大小的数据结构。
- 特点:
- 灵活性高,内存大小不受限制。
- 分配和释放速度较慢,容易导致内存泄漏。
- void function() {
int* ptr = new int; // 在堆中分配内存
*ptr = 20; // 赋值
delete ptr; // 释放内存
}
6.3 全局/静态存储区
- 用途:存储全局变量和静态变量。
- 特点:从程序开始到结束一直存在,自动管理。
-
int globalVar = 100; // 存储在全局存储区
void function() {
static int staticVar = 10; // 存储在全局存储区
}
6.4 代码区(Code Segment)
- 用途:存储程序的可执行代码,通常只读。
- 特点:程序加载时分配。
- void function() {
// 执行代码
}
6.5 内存模型示意图
+-------------------+
| 代码区 |
| (Code Segment) |
+-------------------+
| 全局/静态区 |
| (Global/Static) |
+-------------------+
| 堆 |
| (Heap) |
| |
| |
+-------------------+
| 栈 |
| (Stack) |
+-------------------+
7. 动态分配内存 new
和 delete
-
new
操作符:在堆中分配内存。 -
单个变量分配:
-
int* ptr = new int; // 分配一个整数
*ptr = 10; // 赋值 -
数组分配:
-
int* arr = new int[5]; // 分配一个整数数组
for (int i = 0; i < 5; i++) {
arr[i] = i; // 初始化
} -
delete
操作符:释放内存。 -
单个变量释放:
-
delete ptr; // 释放内存
ptr = nullptr; // 避免悬空指针
-
数组释放:
-
delete[] arr; // 释放动态分配的数组
8. 空指针和野指针
- 空指针:
- 定义:空指针指向无效的内存地址,通常用
nullptr
或NULL
表示。 - 用途:指示指针不指向有效内存。
- int* ptr = nullptr; // 指针未初始化
if (ptr) {
// 不会进入此分支
}
- 定义:空指针指向无效的内存地址,通常用
- 野指针:
- 定义:指向已释放内存或未初始化的指针。
- 原因:释放内存后指针仍然指向原地址,或者未初始化。
- int* ptr = new int(10);
delete ptr; // 释放内存
// ptr 现在是野指针
std::cout << *ptr; // 未定义行为
9. 函数指针和回调函数
-
函数指针:指向函数的指针,可以用来调用函数或将函数作为参数传递。
-
声明和使用:
-
void func(int x) {
std::cout << "Value: " << x << std::endl;
}void (*funcPtr)(int) = &func; // 定义函数指针
funcPtr(5); // 调用函数 -
回调函数:通过函数指针传递的函数,用于在某些事件发生时被调用。
-
示例:
-
void execute(void (*callback)(int)) {
callback(10); // 调用传入的回调函数
}int main() {
execute(func); // 传递 func 函数作为回调
} -
10. 智能指针
C++11 引入智能指针以自动管理内存,减少内存泄漏和错误:
-
std::unique_ptr
:独占所有权,不能复制,只能移动。 -
#include <memory>
void function() {
std::unique_ptr<int> ptr(new int(5)); // 动态分配内存
std::cout << *ptr; // 使用智能指针
} // 超出作用域时自动释放内存 -
std::shared_ptr
:共享所有权,可以多个指针指向同一内存。 -
#include <memory>
void function() {
std::shared_ptr<int> ptr1(new int(5));
std::shared_ptr<int> ptr2 = ptr1; // ptr2 共享 ptr1 的所有权
} // 当所有指向同一内存的指针都超出作用域时,内存被释放 -
std::weak_ptr
:不控制内存的生命周期,避免循环引用。 -
#include <memory>
void function() {
std::shared_ptr<int> ptr1(new int(5));
std::weak_ptr<int> weakPtr = ptr1; // weakPtr 不增加引用计数
} // 当 ptr1 超出作用域时,weakPtr 不会阻止内存释放
11. 总结
- 指针是 C++ 中强大的工具,提供了直接内存访问的能力。
- 正确使用指针和内存管理机制(如
new
、delete
、智能指针)是确保程序安全和高效的关键。 - 理解指针的基本概念、内存管理、函数指针和智能指针是编写健壮 C++ 代码的基础。