C语言指针入门到理解:一篇文章系统梳理指针核心知识(1)
当我们初学C的时候,一看到指针就容易紧张。
其实指针难的地方,不在语法,而在于它把“变量”这层东西,进一步推进到了“内存”这一层。
这篇文章就带你从内存方面出发,一起梳理一下指针相关的核心知识点,包括一下内容:
- 指针到底是什么
&和*分别在做什么- 为什么指针要有类型
const修饰指针怎么区分- 什么是野指针,怎么规避
- 为什么函数想修改外部变量时要用指针
一、什么是指针
想理解指针,先要理解内存和地址。
程序运行时,变量都会放在内存中,而内存会被划分为一个个内存单元。每个内存单元都有编号,这个编号就是地址。CPU 正是通过地址去找到对应的数据。
所以从本质上说:
指针就是地址,指针变量就是专门用来存地址的变量。
你可以把内存理解成一栋宿舍楼:
- 每个房间就是一个内存单元
- 每个房间号就是地址
- 知道房间号,才能准确找到房间
在 C 语言里,这个“房间号”就对应指针。
二、取地址操作符 &
定义一个变量时,本质上是在内存中申请空间,比如:
int a = 10;
如果我们想知道变量 a 在内存中的位置,就要使用取地址操作符 &:
int a = 10;
printf("%p\n", &a);
这里的 &a 表示“取出变量 a 的地址”。
需要注意的是,a 可能占多个字节,但 &a 取到的是这块空间起始位置的地址,也就是较小地址那个字节的位置。
三、指针变量是什么
地址也是数据,既然是数据,就可以存起来。
存地址的变量,就是指针变量。
例如:
int a = 10;
int *pa = &a;
这里 pa 就是一个指针变量,它里面存的是 a 的地址。指针变量本质上也是变量,只不过它存储的是地址。
这句代码可以拆开理解:
pa是变量名*表示pa是指针变量int表示pa指向的是一个int类型的对象
也就是说,int *pa 的真实含义是:
pa是一个指向整型数据的指针。
四、解引用操作符 *
有了地址之后,下一步就是:怎么通过地址访问对应的数据?
答案就是使用解引用操作符 *。
int a = 100;
int *pa = &a;
*pa = 0;
这里的 *pa 表示:根据 pa 中存放的地址,找到对应的那块内存空间。
因为 pa 里存的是 a 的地址,所以 *pa 实际上就是 a 本身。
因此 *pa = 0; 最终修改的就是 a。
所以要分清:
pa是地址*pa是地址对应的内容
五、指针为什么要有类型
很多人会问:
既然指针里存的都是地址,那为什么还要区分 int*、char*、double*?
原因很简单:
指针类型决定了编译器如何看待这块地址。
它主要影响两件事。
1. 决定解引用时访问几个字节
例如:
int n = 0x11223344;
int *pi = &n;
char *pc = (char *)&n;
*pi按int处理,通常一次访问 4 个字节*pc按char处理,一次只访问 1 个字节
这说明,指针类型决定了解引用权限。
2. 决定指针加减的步长
char *pc = (char *)&n;
int *pi = &n;
pc + 1; // 向后移动1个字节
pi + 1; // 向后移动1个int大小
所以:
char* + 1跳过 1 字节int* + 1通常跳过 4 字节
指针加 1,不是简单数值加 1,而是跨过一个对应类型的元素。
六、指针变量的大小
很多同学以为 char* 小一点,double* 大一点,其实不是。(注意,这里是说的指针变脸本身的大小)
指针变量的大小,和它指向什么类型无关,只和平台有关。
一般来说:
- 32 位平台下,指针大小是 4 字节
- 64 位平台下,指针大小是 8 字节
所以在同一平台下,你如果运行下面的代码:
sizeof(char *)
sizeof(int *)
sizeof(double *)
通常结果都是一样的。
七、void* 指针
void* 可以理解成“泛型指针”。
它的特点是:
- 可以接收任意类型的地址
- 常用于函数参数,增强通用性
- 但不能直接解引用
- 也不能直接进行指针加减运算
void* 它适合在函数参数中接收不同类型的数据地址,从而实现一定程度的泛型效果,但本身缺少具体类型信息,因此不能直接参与解引用和常规指针运算。
例如:
void *p = &a;
这样写可以,但不能直接写:
*p = 10;
p + 1;
真正使用前,需要先转换成具体类型。
比如说
void *p = &a;
(int*) p = 10;//强制类型转换为int* 类型的指针
p + 1;
八、const 修饰指针怎么理解
这是初学者特别容易混淆的内容。
1. const int *p
const int *p = &n;
含义是:
p可以改指向- 但不能通过
p修改它指向的内容
也就是:
指向的内容不能改,指针本身能改。
2. int * const p
int * const p = &n;
含义是:
p不能再指向别处- 但可以通过
p修改内容
也就是:
指针本身不能改,指向的内容能改。
3. const int * const p
两边都限制:
- 不能改指向
- 也不能通过它改内容
4. 一个简单记忆方法
看 const 限制谁:
const在*左边,限制的是“指向的内容”const在*右边,限制的是“指针本身”
九、指针运算
指针常见的运算主要有三种:
- 指针
+/-整数 - 指针 - 指针
- 指针关系运算
1. 指针 + 整数
常用于遍历数组:
int arr[5] = {1,2,3,4,5};
int *p = arr;
for(int i = 0; i < 5; i++)
{
printf("%d ", *(p + i));
}
这里 p + i 表示移动到第 i 个元素的位置。
2. 指针 - 指针
常用于计算两个位置之间相差多少个元素。
例如模拟 strlen:
int my_strlen(char *s)
{
char *p = s;
while(*p != '\0')
p++;
return p - s;
}
这里 p - s 表示字符串长度。
3. 指针关系运算
例如:
while(p < arr + sz)
{
printf("%d ", *p);
p++;
}
通过比较指针位置来判断遍历是否结束。
十、什么是野指针
野指针是指针里最危险的问题之一。
野指针定义是:
野指针就是指针指向的位置不可知,或者是不正确、没有明确限制的位置。
简单理解就是:
这个指针已经不知道自己到底在指向哪里了,但你还试图通过它访问内存,那很容易造成各种各样的错误。
十一、野指针的常见成因
1. 指针未初始化
int *p;
*p = 20;
p 里是随机值,直接解引用非常危险。
2. 指针越界访问
int arr[10] = {0};
int *p = arr;
for(int i = 0; i <= 11; i++)
{
*(p++) = i;
}
一旦越过数组合法范围,p 就成了野指针。
3. 返回局部变量地址
int* test()
{
int n = 100;
return &n;
}
函数结束后,局部变量生命周期结束,这个地址就失效了。
十二、如何规避野指针
1. 初始化指针
如果一开始不知道该指向谁,就先赋值为 NULL:
int *p = NULL;
NULL 是一个值为 0 的标识符常量,表示空地址。
2. 不要越界访问
指针只能访问合法申请到的那块内存,不能超范围操作。
3. 不再使用时及时置空
p = NULL;
这样做可以减少误用风险。
4. 使用前检查合法性
if (p != NULL)
{
// 再使用
}
这是非常常见的安全写法。
十三、assert 断言的作用
在使用指针前,经常会先做合法性校验:
assert(p != NULL);
assert 的作用是:如果条件为真,程序继续执行;如果条件为假,程序直接报错终止,并指出问题位置,便于调试。
例如:
int my_strlen(const char *str)
{
assert(str);
int count = 0;
while(*str)
{
count++;
str++;
}
return count;
}
这相当于在函数开头加了一道保护。
十四、指针最重要的应用:传址调用
为什么要学指针?
因为有些问题,单纯传值是解决不了的。
例如交换两个变量:
void Swap1(int x, int y)
{
int tmp = x;
x = y;
y = tmp;
}
这样写交换失败,因为这是传值调用,函数里改的是形参副本,不是原变量。
正确写法应该是传地址:
void Swap2(int *px, int *py)
{
int tmp = *px;
*px = *py;
*py = tmp;
}
调用时:
Swap2(&a, &b);
这时传入的是 a 和 b 的地址,函数内部通过解引用直接修改原变量,所以交换成功。
这也是指针最核心的价值之一:
当函数需要修改主调函数中的变量时,就要用传址调用。
十五、总结
学指针,不能只背语法,主要是你得明白这样一条链路:
变量 -> 内存空间 -> 地址 -> 指针变量 -> 解引用访问
你把它理顺了,自然就会明白:
- 为什么要用
& - 为什么要有
* - 为什么指针类型很重要
- 为什么会有野指针
- 为什么传地址才能改外部变量
ps:各位读完文字后可以思考一下这几个问题哦,读者自证不难(其实答案我上面都算是写出来了)
&spm=1001.2101.3001.5002&articleId=160153809&d=1&t=3&u=69c80baee6e4402faa18650584d21095)
756

被折叠的 条评论
为什么被折叠?



