引用运算符(&):
基本引用
#include <iostream> int main() { int a = 10; int& ref = a; // ref 是 a 的引用,ref 和 a 绑定在一起 ref = 20; // 修改 ref 也会修改 a std::cout << "a: " << a << std::endl; // 输出 20 return 0; }
函数参数中的作用
传递变量的引用:
#include <iostream> void modify(int& x) { x *= 2; // 直接修改原变量 } int main() { int num = 5; modify(num); std::cout << "num: " << num << std::endl; // 输出 10 return 0; }
传递
const
引用(避免复制且保护数据):#include <iostream> void print(const std::string& str) { std::cout << str << std::endl; } int main() { std::string text = "Hello, C++!"; print(text); // 通过引用传递,但 print() 不能修改 text return 0; }
返回值引用:
返回局部变量的引用(⚠️危险,不要这样做)
int& dangerous() { int x = 10; // 局部变量 return x; // ⚠️ 返回局部变量的引用,x 在函数结束后会被销毁 }
可能出现的问题:
崩溃(Segmentation Fault): 访问已销毁的内存,程序直接崩溃
垃圾值:ref指向的内存可能已经被其他代码覆盖,导致不可预测的输出
偶尔看似“正常”:某些情况下,内存未被立即覆盖,代码可能“看起来正常”,但它依然是不安全的。
解决方法:
返回静态变量的引用(安全)
int& dangerous() { static int x = 10; // 局部变量 return x; // ⚠️ 返回局部变量的引用,x 在函数结束后会被销毁 }
static
变量存储在全局数据区,不会在函数返回后销毁。
返回值而不是引用
int dangerous() { int x = 10; return x; }
- 直接返回
int
,触发值拷贝,不会返回悬空引用。
- 直接返回
右值引用(C++)
取地址运算符:
在C++中的&
有两种不同的用途,取决于它出现的上下文:
取地址运算符 (当用在表达式中):返回变量的内存地址
int x = 10; int* ptr = &x; // &x 获取变量x的内存地址
所以在
int* ptr = &x
这个语句中:int*声明ptr是一个指向整数的指针
&x使用取地址运算符获取x的内存地址
整个语句的含义是:创建一个指向整数的指针ptr, 并将它初始化为指向变量x的地址
引用声明符号(当用在类型声明中): 声明一个引用类型
int x = 10; int& ref = x; // 声明ref为x的引用
指针和地址:
在C++中,指针(Pointer)是存储地址(Address)的变量。它用于间接访问和操作内存,是C++语言的重要特性之一。
什么是指针?
指针是一个变量,它存储另一个变量的地址。每个变量都有一个地址,而指针就是用于存储这个地址的变量。
式例:定义指针
#include <iostream> int main() { int a = 10; // 普通变量 int* ptr = &a; // 指针变量,存储 a 的地址 std::cout << "变量 a 的值: " << a << std::endl; std::cout << "变量 a 的地址: " << &a << std::endl; std::cout << "指针 ptr 的值: " << ptr << std::endl; std::cout << "指针 ptr 指向的值: " << *ptr << std::endl; // 解引用 return 0; } /** 变量 a 的值: 10 变量 a 的地址: 0x7ffeefbff5c4 指针 ptr 的值: 0x7ffeefbff5c4 指针 ptr 指向的值: 10 /
&a
获取变量a
的地址ptr
存储a
的地址*ptr
(解引用)访问ptr
指向的值。
指针的基本操作
修改指针指向的值:
int a = 10; int* ptr = &a; *ptr = 20; // 通过指针修改 a 的值 std::cout << a; // 输出 20
*ptr = 20;
通过指针修改a
的值
指针的赋值:
int a = 5, b = 10; int* ptr = &a; ptr = &b; // 指针现在指向 b
指针的初始化:
| 方式 | 示例 | 说明 | | --- | --- | --- | | 指向变量 |
int* p = &a;
| 指向p
存储a
的地址 | | 指向nullptr
|int* p = nullptr
|nullptr
代表空指针 | | 指向数组 |int arr[5] = {1, 2, 3, 4, 5}; int* p = arr;
|p
指向 |
指针与数组
数组变量本质上是指针常量,它的值是数组的首地址。
示例:数组和指针
#include <iostream>
int main() {
int arr[] = {10, 20, 30};
int* ptr = arr; // 等价于 &arr[0]
std::cout << *ptr << std::endl; // 10
std::cout << *(ptr + 1) << std::endl; // 20
std::cout << *(ptr + 2) << std::endl; // 30
return 0;
}
ptr + 1
向后移动一个int
大小,指向arr[1]
指针和动态内存分配
(1)使用new
申请动态内存:
int* p = new int(42); // 在堆上创建一个 int 变量,值为 42
std::cout << *p; // 输出 42
delete p; // 释放内存
new
申请堆内存,必须用delete
释放,否则会内存泄漏
(2)动态数组
int* arr = new int[5]; // 申请 5 个整数
arr[0] = 10;
arr[1] = 20;
delete[] arr; // 释放数组
delete[] arr;
释放动态数组。
指针的类型
指针类型 | 示例 | 说明 |
普通指针 | int* p = &a; | 存储变量地址 |
常量指针 | const int* p = &a; | 不能修改指向的值 |
指针常量 | int* const p = &a; | 不能修改指针的地址 |
空指针 | int* p = nullptr; | 指向nullptr ,避免野指针 |
野指针(⚠️危险) | int* p; | 未初始化,指向未知地址,可能崩溃 |
栈内存 vs 堆内存
栈内存(Stack)
栈(Stack)是一种自动管理内存区域,用于存储局部变量和函数调用信息。
栈的特点:
自动分配和释放
当函数被调用时,局部变量会被自动分配到栈上
当函数返回时,变量会自动销毁,无需手动释放内存
内存大小受限
栈的大小是有限的,通常由操作系统设置
如果在栈上分配了过大的数组或者递归过深,可能会导致栈溢出(Stack Overflow)
访问速度快:
- 由于栈采用LIFO(后进先出)结构,内存访问速度非常快,比堆快得多
变量作用域受限:
- 栈上的变量只能在函数内部存活,一旦函数执行完毕,变量就会被销毁。
示例:
void example() {
int x = 10; // x 存储在栈上
// 当 example() 结束时,x 会被自动释放
}
堆内存(Heap):
堆(Heap)是一种用于动态分配内存的区域,程序可以在运行时手动管理它
堆的特点:
手动分配和释放:
堆上的内存必须手动分配(
new
)和手动释放(delete
)如果没有正确释放,可能会导致内存泄漏(Memory Leak)
内存大小较大:
- 堆比栈大得多,但需要谨慎管理,以避免占用过多资源。
访问速度较慢:
- 由于堆内存是动态分配的,系统需要额外的操作来管理它,因此访问速度比栈慢。
变量作用域可控:
- 在队上创建的对象不会自动销毁,它们可以在多个函数间共享,直到显式释放。
对比项 | 栈(Stack) | 堆(Heap) |
内存管理方式 | 自动(函数调用时分配,函数返回时释放) | 手动(new 分配,delete 释放) |
内存大小 | 较小(受操作系统限制) | 较大(取决于系统可用内存) |
访问速度 | 快(LIFO 结构,缓存友好) | 慢(动态分配,额外管理开销) |
变量作用域 | 仅在函数内部有效,函数结束后自动释放 | 只要不 delete ,变量可以持续存在 |
典型问题 | 栈溢出(过深递归、大数组) | 内存泄漏(忘记 delete ) |
适用场景 | 局部变量、小型对象 | 需要长期存活或较大对象 |