文章目录
0. 前言
📣按照国际惯例,首先声明:本文只是我自己学习的理解,虽然参考了他人的宝贵见解及成果,但是内容可能存在不准确的地方。如果发现文中错误,希望批评指正,共同进步。
本文将通过下面四个(两个符号用在两种场合)重要符号,详细解释 C/C++ 中的指针。
符号 | 名称 | 语言环境 | 含义说明 |
---|---|---|---|
* | 指针声明符 | C/C++ | 声明一个指针变量 |
& | 取地址运算符 | C/C++ | 获取变量的内存地址 |
* | 解引用运算符 | C/C++ | 访问指针所指向的内容 |
& | 引用声明符 | C++ | 给变量起别名 |
注意区分上面说的声明符和运算符:声明符用于变量声明中的类型描述,运算符用于表达式中的操作。
1. *
—— 指针声明符(Pointer Declaration)
在 C/C++ 中,*
用于在开头声明一个指针变量。
示例(C):
int *p; // p 是一个指向 int 类型的指针
示例(C++):
double *d; // d 是一个指向 double 类型的指针
📝 注意:在 C 语言中,
int* a, b;
表示只有a
是指针,而b
是普通整型。不要误以为是两个指针!
2. &
—— 取地址运算符(Address-of Operator)
在 C/C++ 中,&
是用于获取变量的内存地址运算符。
示例:
int x = 10;
int *p = &x; // 将 x 的地址赋值给指针 p
此时,p
存储的是 x
的地址,而不是 x
的值。
输出地址:
printf("x 的地址:%p\n",& x);
printf("p 的值:%p\n", p);
输出相同,因为 p == &x
。
3. *
—— 解引用运算符(Dereference Operator)
使用 *
运算符来访问指针所指向的内存中的值。
示例:
int x = 20;
int *p = &x;
printf("*p = %d\n", *p); // 输出 20
*p = 30; // 修改指针指向的内容
printf("x = %d\n", x); // 输出 30
p
是变量x
的地址*p
是这个地址中保存的数据(即x
的值)
4. &
—— 引用声明符(Reference Declaration)【仅限 C++】
在 C++ 中,&
还可以用于定义“引用”,即变量的别名。
示例:
#include<stdio.h>
#include <iostream>
int main()
{
int x = 42;
int& ref = x; //ref是x的引用(别名)
ref = 99;
std::cout << "x=" << x << std::endl;
return 0;
}
引用的特点:
- 必须初始化。
- 通常用于函数参数传递,避免拷贝。
- 初始化后不能改变引用目标,不能重新绑定,请见下例:
#include<stdio.h>
#include <iostream>
int main()
{
int x = 42;
int y = 88;
int& ref = x; //ref是x的引用(别名)
int& ref = y; //重复引用
return 0;
}
输出错误提示:
引用有什么用?
你可能会想问引用只是给变量取一个别名,这有什么意义呢?
其实学过Python的同学在此应该会有熟悉的感觉,这其实就是in-place操作。
-
性能优势
- 避免拷贝:对于大型对象(如类实例、容器等),传递引用比传递对象本身要高效得多,因为不需要进行深拷贝。
- 减少内存占用:由于引用只是变量的一个别名,不占用额外的内存空间。
-
简化语法
- 相比于指针,引用提供了更自然的语法。你不需要担心空指针问题,并且引用总是有效的(除非引用本身未初始化)。
-
增强功能
- 支持多态:当你传递基类类型的引用时,如果实际传递的是派生类的对象,则可以在函数内部利用多态性调用派生类的方法。
- 实现链式编程风格:通过返回引用,可以让方法调用链接起来,形成流畅的API设计。
5. 对比总结表
运算符 | 语言 | 出现场景 | 功能说明 | 示例 |
---|---|---|---|---|
* | C/C++ | 声明语句中 | 声明一个指针类型 | int *p; |
& | C/C++ | 表达式中 | 获取变量的地址 | int *p = &x; |
* | C/C++ | 表达式中 | 解引用,访问指针指向的内容 | *p = 10; |
& | C++ | 声明语句中 | 声明引用(别名) | int &ref = x; |
✅ 总结口诀(帮助记忆)
*
在声明中 → 是指针&
在表达式中 → 是取地址*
在表达式中 → 是解引用&
在 C++ 声明中 → 是引用
6. 复杂情况举例分析
示例 1:多级指针(二级指针)
#include<stdio.h>
#include <iostream>
int main()
{
int x = 10;
int* p = &x; //p是一级指针
int** pp = &p; //pp是二级指针
printf("p是%p\n", p);
printf("pp是%p\n", pp);
printf("pp进行1次解引用%p\n", *pp); //1次解引用后仍是地址
printf("pp进行2次解引用%d\n", **pp); //2次解引用后是int型数值
}
*pp
得到的是p
**pp
得到的是*p
,也就是x
输出为:
示例 2:函数中使用引用(C++)
#include<stdio.h>
#include <iostream>
void add1(int& num) //函数无返回值,直接修改外部变量
{
num++;
}
int main()
{
int x = 0;
add1(x);
std::cout << x << std::endl;
}
参数 num
是原始变量的别名(直接操作内存中的值),add1
函数没有返回值,而是通过引用实现对实参的修改。
输出为:
示例 3:函数中使用指针(C)
#include<stdio.h>
#include <iostream>
void add1(int* num) //函数无返回值,直接修改外部变量
{
(*num)++;
}
int main()
{
int x = 0;
add1(&x);
std::cout << x << std::endl;
}
效果与上例相同,但语法不同。
7. 指针使用常见错误
错误类型 | 错误示例 | 问题分析 | 修正方法 |
---|---|---|---|
野指针 | int *p; *p = 10; | 指针 p 未初始化,指向随机内存地址,解引用会导致未定义行为(崩溃或数据损坏)。 | 初始化指针:int x; int *p = &x; |
悬垂指针 | int *p = &x; free(&x); *p = 5; | 指针 p 指向的内存已被释放(如变量 x 离开作用域),但 p 仍被使用,导致非法访问。 | 释放后置空:p = NULL; (或确保作用域有效) |
类型不匹配的指针 | double d = 3.14; int *p = &d; | 将 double* 强制赋给 int* ,解引用时因类型解释错误导致数据读取错误。 | 确保指针类型与目标类型一致。 |
指针越界访问 | int arr[3] = {1,2,3}; int *p = arr; *(p+3) = 4; | 指针算术越界(访问 arr[3] ),破坏相邻内存或触发段错误。 | 严格限制指针移动范围:0 ≤ i < 数组长度 。 |
返回局部变量指针 | int* func() { int x=10; return &x; } | 返回局部变量 x 的地址,但 x 在函数退出后销毁,返回的指针无效。 | 返回动态内存或静态变量地址:static int x; |