指针和引用是 C++ 中用于间接访问变量的重要机制,两者都能提高代码的灵活性和效率,但在语法、语义和使用场景上有显著区别。
1、指针
指针是存储另一个变量内存地址的变量 ,其本质是一个独立的变量,拥有自己的内存空间,存储的内容是目标变量的地址(通常为 4 字节或 8 字节,取决于系统位数)。
1.1 基本概念与语法
cpp
int main() {
int number = 42; // 一个普通的整型变量
int* ptr = &number; // 一个指针变量,存储了number的地址
std::cout << "Value of number: " << number << std::endl; // 输出: 42
std::cout << "Address of number: " << &number << std::endl; // 输出: 0x7ffd2d5e7a4c (类似的值)
std::cout << "Value of ptr: " << ptr << std::endl; // 输出: 0x7ffd2d5e7a4c (和&number一样)
std::cout << "Value pointed to by ptr: " << *ptr << std::endl; // 输出: 42 (解引用)
return 0;
}
&
(取地址运算符):获取变量的内存地址。*
(解引用运算符):访问指针所指向地址中存储的值。- 声明:
type* pointer_name;
(例如int* p;
,double* dPtr;
)
1.2 指针的运算
指针支持有限的算术运算:++, --, +, -。运算的单位是其所指向类型的大小。
cpp
int arr[] = {10, 20, 30, 40};
int* arrPtr = arr; // 数组名可视为指向其第一个元素的指针
std::cout << *arrPtr << std::endl; // 输出: 10
arrPtr++; // 指针向前移动 sizeof(int) 个字节
std::cout << *arrPtr << std::endl; // 输出: 20
arrPtr += 2;
std::cout << *arrPtr << std::endl; // 输出: 40
1.3 指针与常量 (const)
const 和指针的组合容易混淆,关键在于 const 修饰的是什么。
cpp
int value = 10;
int anotherValue = 20;
// 1. 常量指针 (Pointer to constant)
// 指针指向的值不能通过该指针修改,但指针本身可以指向别的地址。
const int* ptr1 = &value;
// *ptr1 = 30; // 错误:不能修改指向的值
ptr1 = &anotherValue; // 正确:可以修改指针的指向
// 2. 常量指针 (Constant pointer)
// 指针本身的值(存储的地址)不能改变,但它指向的值可以改变。
int* const ptr2 = &value;
*ptr2 = 30; // 正确:可以修改指向的值
// ptr2 = &anotherValue; // 错误:不能修改指针的指向
// 3. 指向常量的常量指针 (Constant pointer to constant)
// 指针的指向和指向的值都不能改变。
const int* const ptr3 = &value;
// *ptr3 = 30; // 错误
// ptr3 = &anotherValue; // 错误
总结:
- 根据英文翻译记名字,const :常量指针; const:指针常量
- 根据const修饰记哪些不能改,
const int* ptr1
:const修饰指针,则不能给*ptr1
赋值;int* const ptr2
: const修饰ptr2,则不能给ptr2赋值
1.4 动态内存管理
指针是 C++ 中手动进行动态内存分配和释放的关键。
cpp
// 动态分配单个int
int* dynamicInt = new int(100);
std::cout << *dynamicInt << std::endl; // 输出: 100
// 动态分配数组
int size = 5;
int* dynamicArray = new int[size]{1, 2, 3, 4, 5}; // C++11 初始化
// 使用后必须手动释放,否则会导致内存泄漏
delete dynamicInt; // 释放单个对象
delete[] dynamicArray; // 释放数组
dynamicInt = nullptr; // 良好实践:释放后立即将指针置为空,防止"悬空指针"
dynamicArray = nullptr;
1.5 多级指针
指针可以指向另一个指针。
cpp
int a = 5;
int* ptr = &a;
int** pptr = &ptr; // 指向指针的指针
std::cout << **pptr << std::endl; // 输出: 5 (需要两次解引用)
1.6 指针的常见问题
-
空指针 (Null Pointer) :
int* ptr = nullptr;
(C++11+) 或NULL
(C)。指向地址 0,表示"不指向任何对象"。解引用空指针会导致未定义行为(通常是程序崩溃)。 -
悬空指针 (Dangling Pointer) :指针指向的内存已被释放 。解引用它会导致未定义行为。
cppint* ptr = new int(10); delete ptr; // 内存被释放 // ptr 现在是一个悬空指针 // *ptr = 20; // 危险!未定义行为 ptr = nullptr; // 正确的做法:释放后置空
-
野指针 (Wild Pointer) :未被初始化的指针 。其值是随机的垃圾地址。绝对不要解引用野指针 。
cppint* wildPtr; // 野指针 // *wildPtr = 10; // 极度危险!未定义行为
2、引用
引用是已存在变量的别名 ,它不占用独立的内存空间,与被引用的变量共享同一块内存。引用必须在定义时初始化,且一旦绑定到某个变量,就不能再改为绑定其他变量 。它本质上是一个解引用的、不可为空的、不可重新绑定的常量指针。
2.1 基本概念与语法
cpp
int main() {
int original = 50;
int& ref = original; // ref 是 original 的引用(别名)
std::cout << original << std::endl; // 输出: 50
std::cout << ref << std::endl; // 输出: 50
ref = 100; // 通过引用修改原变量的值
std::cout << original << std::endl; // 输出: 100
std::cout << ref << std::endl; // 输出: 100
// int& badRef; // 错误!引用必须在声明时初始化。
return 0;
}
- 必须在创建时初始化 ,且一旦初始化绑定到一个变量,就不能再成为其他变量的引用。
- 对引用的所有操作都是在操作它所引用的原始变量。
2.2 常量引用
常量引用常用于函数参数,以避免不必要的拷贝,同时保证不会意外修改传入的实参。它可以绑定到临时对象(右值)。
cpp
void printValue(const int& value) { // 使用const引用作为参数
std::cout << value << std::endl;
// value = 10; // 错误:不能通过const引用修改值
}
int main() {
int a = 5;
printValue(a); // OK
printValue(10); // OK!常量引用可以绑定到临时对象(字面量10)
return 0;
}
2.3 引用与函数
引用最重要的用途之一是在函数参数传递和返回值中。
2.3.1 按引用传递
允许函数修改实参的值,且避免大对象拷贝的开销。
cpp
void swap(int& a, int& b) {
int temp = a;
a = b;
b = temp;
}
int main() {
int x = 10, y = 20;
swap(x, y);
std::cout << x << ", " << y; // 输出: 20, 10
return 0;
}
2.3.2 按引用返回
可以返回函数调用者可以修改的左值。**绝不能返回局部变量的引用!**核心原因是局部变量的内存生命周期与引用的有效性不匹配,会导致 "悬空引用",引发未定义行为。
局部变量(如函数内定义的变量)存储在栈区 ,其生命周期仅限于定义它的代码块 (如函数执行期间)。当代码块执行结束(如函数返回),栈帧会被销毁 ,局部变量的内存会被释放(变为 "无效内存")。
若此时返回该变量的引用,这个引用会指向一块 "已失效的内存"(即 "悬空引用")。后续对该引用的任何操作(如读取、修改)都会触发未定义行为(可能读取到垃圾值、程序崩溃、或出现不可预测的结果)。
cpp
int& getElement(int arr[], int index) {
return arr[index]; // 返回数组元素的引用是安全的
}
int main() {
int array[] = {1, 2, 3};
getElement(array, 1) = 50; // 修改array[1]为50
std::cout << array[1]; // 输出: 50
return 0;
}
3、常见问题
-
指针和引用的本质区别是什么?
- 指针是存储地址的独立变量 ,有自己的内存空间;引用是变量的别名,无独立内存空间,与被引用变量共享内存。
- 指针可以为
nullptr
,引用必须绑定有效变量(无空引用)。 - 指针可以重新赋值指向其他变量,引用一旦绑定则不可改向。
-
为什么引用必须初始化?
引用的本质是"别名",语法上要求必须从定义时就明确它是谁的别名。如果允许未初始化的引用,就无法确定它指向哪个变量,会导致无法预测的内存操作(未定义行为)。这是 C++ 语法对引用的强制约束,以保证安全性。
-
函数参数传递时,何时用指针,何时用引用?
-
用引用:
- 需修改实参的值,且能保证参数非空(无需检查
nullptr
)。 - 传递大型对象(避免复制,与指针效率相同,但语法更简洁)。
- 运算符重载(如
operator+
、operator=
通常用引用返回)。
- 需修改实参的值,且能保证参数非空(无需检查
-
用指针:
- 允许参数为空(需在函数内检查
nullptr
,如delete
操作)。 - 需要传递多级间接地址(如
int**
用于修改指针本身)。 - 与 C 语言兼容(C 中无引用,只能用指针)。
- 允许参数为空(需在函数内检查
-
-
什么是"野指针"?如何避免?
"野指针"是指向无效内存 (如已释放的内存、未初始化的内存)的指针 ,访问野指针会导致未定义行为(崩溃、数据错误等)。
避免方法:
- 指针定义时初始化(如
int* p = nullptr
)。 - 释放内存后及时置空(
delete p; p = nullptr
)。 - 不返回局部变量的指针。
- 函数接收指针参数时,先检查是否为
nullptr
。
- 指针定义时初始化(如
-
const int*
,int* const
, 和const int* const
有什么区别?const int*
- 指向常量的指针(值不能改);int* const
- 常量指针(指针本身不能改);const int* const
- 指向常量的常量指针(值和指针都不能改)。
-
什么是悬空指针?如何避免?
指向已被释放的内存 的指针。
避免方法:- 释放后立即将指针置为
nullptr
; - 使用智能指针。
- 释放后立即将指针置为
-
void*
指针是什么?是一种可以指向任何数据类型(未指定类型)的通用指针。不能直接解引用,必须先强制转换为具体的类型。