什么是指针
指针(Pointer):指针是一个变量类型,它存储的是内存地址,而不是数据值本身。指针变量用于指向内存中的某个位置,通过指针可以间接地访问或修改该位置的数据。在CSP中理解指针的基本概念即可。
内存的本质
计算机的内存(memory)是一块用于存储数据的空间,由一系列连续的存储单元(bit/Byte)组成。
由于1个bit只能表示两个状态,因为我们将每8个bit分为一组,命名为Byte,并将Byte做为内存的最小单元,为了区分每个存储单元,给每个Byte一个唯一的编号(这个编号就叫内存单元的地址)
变量的本质
当在代码中定义一个变量,例如int a=10时,实际发生的动作时程序向内存申请了一块空间(对int来说是4个单元)。然后依次将每个字节的数据写入到对应的内存单元中。
定义变量之后,变量名称可以直接表示变量的值,如果想要获得变量的地址,可以通过取地址运算符&来实现
int a=10;
cout<<&a<<endl; //输出变量a所占内存空间(4个byte)的起始地址,&为取地址符
输出结果大概会是像 0x73fe4c 这样的一串(十六进制)数字,每次运行的结果可能都不一样,因为计算机给变量分配的内存空间是随机的,这个数字就是变量a的起始地址编号,即int类型的4个字节中,编号最小的那一个。在C++中,以第一个字节的地址(起始地址或首地址)编号作为整个变量的地址。
指针的定义和使用
创建指针变量:
int *p;
指针变量的定义跟普通数值型变量的定义并没有本质区别,只是需要在每个变量名前加一个*修饰
间接取值:
cout << *p;
通过取内容运算符(*,也称解引用),可以把指针指向的内存中的数据取出来。
#include<bits/stdc++.h>
using namespace std;
int main() {
//定义整数变量x,存储值为10
int x=10;
//&:表示获取变量x的地址
//定义整型指针,存储整型变量x的地址
int *p=&x;
//p是指针变量(int*)
//*p不是地址,是p这个地址指向的值
cout<<p<<" "<<*p<<endl;
//通过指针修改其指向的值
*p=*p+1;
cout<<x<<endl;
return 0;
}
[info] 注意
创建指针变量时的
*
和创建完指针变量间接取值时的*
是完全不同的概念。
图解指针
创建 a、b 变量,并在内存中分配空间。
创建指向指针变量 p1、p2 并在内存中分配空间。
将 p1 指向 a,即指针变量 p1 中保存 普通变量 a 的地址。
函数参数的传递
值传递
void swap1(int x, int y) /* 定义中的x,y变量被称为swap1函数的形式参数 */
{
int tmp;
tmp = x;
x = y;
y = tmp;
printf("x = %d, y = %d \n", x, y);
}
int main() {
int a = 4,b = 6;
swap1(a, b); /*a,b 变量为 swap1 函数的实际参数。*/
printf("a = %d, b = %d \n", a, b);
return 0;
}
//输出结果
x = 6, y = 4
a = 4, b = 6
函数只是把 a、b 的 值通过拷贝赋值传递给了 x、y,函数里头操作的只是 x、y 的值并不是 a、b 的值。 这就是所谓的参数的值传递了
引用传递
void swap2(int &x, int &y) /* 定义的形式参数的格式与值传递不同 */
{
int tmp = x;
x = y;
y = tmp;
printf("x = %d, y = %d \n", x, y);
}
int main() {
int a = 4,b = 6;
swap2(a, b); /*注意:这里调用方式与值传递一样*/
printf("a = %d, b = %d \n", a, b);
return 0;
}
函数形参x、y 前都有一个取地址符号“&”。有 了这个,调用 swap2 时函数会将 a、b 分别代替了 x、y 了,我们称:x、y 分别引用了 a、b 变量。这样函数里头操作的其实就是实参 a、b 本身,也 是说函数里是可以直接修改到 a、b 的值
地址传递
void swap3(int *x, int *y)
{
int tmp;
tmp = *x;
*x = *y;
*y = tmp;
printf("x = %d, y = %d \n", *x, *y);
}
int main() {
int a = 4,b = 6;
swap3(&a, &b);
printf("a = %d, b = %d \n", a, b);
return 0;
}
//输出结果
x = 6, y = 4
a = 6, b = 4
指针 x、y 的值已经分别是 a、b 变量的地址值了。接下来,对*x、*y 的操作就是对 a、b 变量本身的操作了。所以函数里头的交换就是对 a、b 值的交换了, 这就是所谓的地址传递。
指针访问数组
什么时候使用指针
- 动态内存分配
使用
new
和delete
运算符进行动态内存分配和释放。
int *array = new int[10]; // 分配一个大小为 10 的 int 数组
delete[] array; // 释放分配的内存
实现复杂数据结构 在实现链表、树、图等动态数据结构时,指针是必要的。
函数参数传递 当需要在函数内部修改传入的参数,或者传递大型对象(避免拷贝开销)时,可以使用指针。
void modifyValue(int* ptr) { *ptr = 100; }
数组和指针运算 指针可以用于遍历数组、进行指针算术运算等。
int arr[5] = {1, 2, 3, 4, 5}; int* ptr = arr; for (int i = 0; i < 5; ++i) { cout << *(ptr + i) << " "; }
为什么要使用指针?
- 灵活性 指针提供了对内存的直接访问权限,可以高效地管理和操作内存。-
- 动态性 可以在运行时动态地分配和释放内存,适应程序的需要。
- 高效性 通过指针传递参数,避免了大对象的拷贝,提高了程序的性能。
什么是引用
引用是已有变量的别名,它是已存在变量的另一个名字。一旦将引用初始化为某个变量,就无法再改变其指向。
引用的声明和使用
int a = 10;
int &ref = a; // ref 是变量 a 的引用
ref = 20; // 现在 a 的值也变为 20
cout << a; // 输出 20
什么时候使用引用?
- 函数参数传递 当希望在函数内部修改参数的值,或者传递大型对象时,使用引用。
void modifyValue(int& ref) {
ref = 100;
}
- 范围 for 循环 使用引用避免拷贝,提高效率。
for (auto& item : container) { // 修改 item }
为什么要使用引用?
- 安全性
引用必须在声明时初始化,不能为
nullptr
,降低了空指针引发错误的风险。 - 简洁性 语法上更简洁,使用起来像普通变量,无需解引用。
- 效率 与指针相比,引用的使用减少了指针解引用的开销(在许多情况下,编译器会优化)。
- 易读性 代码更加清晰,容易理解传递的是引用关系。
指针和引用的区别
特性 | 指针 | 引用 |
---|---|---|
是否可为空 | 可以为空(nullptr) | 不能为空,必须绑定到有效对象 |
初始化后是否可变 | 可以重新指向其他对象 | 绑定后不可更改 |
语法 | 使用 *、->、& 等运算符 | 使用与普通变量相同的语法 |
是否需要解引用 | 需要使用 * 运算符解引用 | 自动解引用,直接使用 |
常量性 | 可以有 const 指针,指向常量或非常量 | 可以有对常量的引用 |
内存地址获取 | 使用指针本身即可表示地址 | 使用 & 获取引用对象的地址 |
如何选择使用指针还是引用?
1. 使用引用的场合
- 参数传递 当函数需要修改参数值,或者避免拷贝大对象时。
void process(const MyClass &obj);
2. 使用指针的场合
- 需要空值
当可能不存在对象,需要表示空(
nullptr
)的情况。 - 动态内存管理 需要动态分配和释放内存时。
- 复杂数据结构 实现链表、树等需要链接节点的结构。
总结
指针
- 优势:灵活、功能强大,可为空、可动态管理内存。
- 劣势:使用不当可能导致内存泄漏、悬空指针、难以调试。
引用
- 优势:语法简洁、安全,避免空指针错误。
- 劣势:绑定后不可更改,不能表示空值。
选择指南
- 优先使用引用:当不需要表示空值,且不需要重新绑定时。
- 使用指针:当需要动态管理内存、处理空值、重新指向不同对象时。
引用安全少烦恼,指针灵活需防飘。
性能优化先引用,动态结构指针瞧。