前言:都说指针是C语言的灵魂,同时也是无数初学者的五指山。很多同学指针只会死记
&和*,写代码全靠猜,遇到内存调试直接一头雾水。
指针的本质并不复杂,他是内存的门牌号,我们从内存的底层出发、把指针最基础的核心逻辑讲透,读懂本文,那你就踏入指针的门槛了。
一、内存、地址与指针到底是什么?
我们可以把计算机内存类比成一栋宿舍楼。
宿舍楼里有许许多多房间,如果不给房间编上门牌号,找人就只能一间一间挨个敲门,效率极低。给每个房间分配唯一门牌号之后,只要拿到门牌号,就可以瞬间定位到人。
计算机内存也是一模一样的逻辑:
- 内存会被切分成无数个内存单元,每个单元固定占 1 字节(8bit),这就是宿舍楼的一个个小房间。
- 每一个内存单元,都会分配唯一编号,这个编号就叫做内存地址,对应宿舍的门牌号。
- 在 C 语言的概念里:内存单元编号 = 地址 = 指针。
硬件层面:地址总线完成编址
CPU 想要读写内存数据,需要硬件总线完成通信,其中地址总线专门用来传递地址编号。
- 32 位机器拥有 32 根地址总线,每根线代表 0 或者 1,总共可以产生
2^32种地址编号,所以 32 位可以寻址 4GB 内存空间。- 64 位机器拥有 64 根地址总线,可以表示海量的内存地址。
小补充单位换算:
1 Byte = 8 bit
1 KB = 1024 Byte
1 MB = 1024 KB
1 GB = 1024 MB
重点:变量不是只有一个地址!
比如int a = 10;,int 占 4 字节,会占用连续 4 个内存单元,拥有 4 个地址。&a取到的是这 4 个字节里最小的那个首地址。CPU 拿到首地址,结合类型,就可以访问后续连续的若干字节。
二、指针变量:用来存放地址的变量
地址(指针)本身是一串十六进制数字,比如
0x006FFD70,我们需要一个专门的变量把这个地址存起来,这个变量就是指针变量。
2.1 取地址符&
&叫做取地址操作符,作用:取出变量在内存当中的首地址。
#include <stdio.h>
int main()
{
int a = 10;
printf("%p\n", &a); // %p专门打印地址
return 0;
}
运行输出一串十六进制地址,这就是变量a的首地址。
2.2 指针变量的定义
int a=10;
int * ps=&a;
拆解
int * ps:
ps是变量名字;*告诉编译器:ps 是一个指针变量;- 前面的
int代表:ps 这个指针,指向的对象是 int 类型。
同理:
char c ="cylnk";
char * pz=&c;//char *存放char类型变量的地址
所以ps 这个变量内部存储的值就是 a 的首地址,通过 ps,就能找到变量 a 所在的内存空间。
2.3 解引用操作符*
`*``放在指针变量前面,叫做解引用:拿着指针里面存的地址,找到对应的内存空间。
#include <stdio.h>
int main()
{
int y = 100;
int* ps = &y;
*ps = 0; // 通过ps中存储的地址,找到y,把y赋值为0
return 0;
}
这里
*pa等价于变量 a。修改*pa,本质就是修改内存中 a 的值。
很多同学疑惑:直接写a=0不行吗,为什么要用指针?
指针提供了第二条修改内存数据的通路。后续写函数传参、操作数组、动态内存的时候,这个特性会发挥巨大作用。
⚠️注意区分两个 *:
- 定义的时候的
*:仅仅标记这是指针变量;- 使用时的
*pa:代表解引用操作,访问指针指向的内存。
2.4 指针变量占多大内存?
指针变量存储的是地址,地址的位数由平台决定,和指针是什么类型无关。
- 32 位平台:地址是 32bit,所有指针大小都是 4 字节
- 64 位平台:地址是 64bit,所有指针大小都是 8 字节
示例:
#include <stdio.h>
int main()
{
printf("%zd\n", sizeof(char *));
printf("%zd\n", sizeof(short *));
printf("%zd\n", sizeof(int *));
printf("%zd\n", sizeof(double *));
return 0;
}
不管是
char*还是int*,同一个平台下大小完全一样。
思考:既然大小全部一样,那为什么还需要区分char*、int*不同指针类型?答案在下一节揭晓。嘻嘻
三、指针类型到底有什么意义?
误区:既然指针变量大小全部一样,那类型就没用?大错特错!
指针的类型决定两件核心事情:**解引用时访问多少字节;指针 ± 整数的时候,向后跳多少字节(步长)**±这个符号的输入方式是win+句号就和触发一个神秘的输入方式哦。
3.1 指针类型决定解引用权限(一次操作几个字节)
代码:
int n = 0x11223344;
int *pi = &n;
*pi = 0;
int*解引用权限 4 字节,执行后 n 的全部 4 个字节都会置 0。
代码:强制转换成char *
int n = 0x11223344;
char *pc = (char *)&n;
*pc = 0;
char*解引用权限只有 1 字节,只会修改 n 的第一个字节,剩下 3 字节保持原样。
总结:
char*:解引用只访问 1 字节;
short*:解引用访问 2 字节;
int*:解引用访问 4 字节。
3.2 指针类型决定指针± 整数的步长
指针加 1,不是内存地址简单 + 1 字节,而是向后跳「指向类型的大小」个字节。
#include <stdio.h>
int main()
{
int n = 10;
char *pc = (char*)&n;
int *pi = &n;
printf("%p\n", pc);
printf("%p\n", pc+1); // char+1:地址+1字节
printf("%p\n", pi);
printf("%p\n", pi+1); // int+1:地址+4字节
return 0;
}
char* + 1:向后移动 1 个字节int* +1:向后移动 4 个字节double* +1:向后移动 8 个字节核心结论:
指针+N,实际地址变化 = 原始地址 + N × sizeof (指向的类型)。
3.3 特殊的void*泛指指针
void*是无具体类型的指针,又叫泛型指针。
优点:可以接收任意类型变量的地址,不会报类型不匹配警告。
限制:不能直接解引用,也不能直接 ± 整数运算,编译器不知道它指向的对象占几个字节。
#include <stdio.h>
int main()
{
int a = 10;
void* pa = &a;
// *pa = 20; // 编译报错,非法间接寻址
// pa + 1; // 编译报错,无法计算步长
return 0;
}
void * 一般用作函数形参,实现类似 “泛型” 效果,一个函数接收多种不同类型数据地址。想要读写,需要强制转换成对应类型指针。
四、指针的三种运算
指针不是什么运算都支持,C 语言指针合法运算只有三类:指针 ± 整数、指针 - 指针、指针关系比较。大部分运算(指针 + 指针,指针 * 指针)都是非法的。
4.1 指针± 整数
配合数组使用最多。数组内存连续存放,首元素地址 + 下标,就能访问数组每一个元素。
#include <stdio.h>
int main()
{
int arr[10] = {1,2,3,4,5,6,7,8,9,10};
int *p = &arr[0];
int sz = sizeof(arr)/sizeof(arr[0]);
for(int i=0; i<sz; i++)
{
printf("%d ", *(p+i));
}
return 0;
}
*(p+i)等价于arr[i],数组下标本质就是指针运算的语法糖。
4.2 指针 - 指针
前提:两个指针必须指向同一块连续内存(同一个数组)。
指针相减,得到的不是地址直接相减的字节数,而是两个指针中间相隔元素的个数。
经典手搓字符串长度my_strlen:
#include <stdio.h>
int my_strlen(char *s)
{
char *p = s;
while(*p != '\0')
{
p++;
}
return p - s; // 两个char*指针相减,得到字符数量
}
int main()
{
printf("%d\n", my_strlen("abc"));
return 0;
}
4.3 指针的关系运算(大小比较)
同样要求指针指向同一块连续内存,可以用
> < >= <=比较地址先后。
#include <stdio.h>
int main()
{
int arr[10] = {1,2,3,4,5,6,7,8,9,10};
int *p = &arr[0];
while(p < arr + 10)
{
printf("%d ", *p);
p++;
}
return 0;
}
arr+10指向数组末尾的后一位,C 语言允许指针指向数组末尾的下一个位置,只做比较,禁止解引用访问。
五、核心知识点复盘
- 指针 = 内存单元的地址;指针变量是专门存放地址的变量。
&取地址;*解引用,通过地址找到内存空间。- 指针变量大小只看操作系统平台,和指针类型无关;32 位 4 字节,64 位 8 字节。
- 指针类型两大意义:
- 解引用时,决定一次可以访问几个字节;
- 指针 ± 整数,决定步长,+1 跳过一个指向元素的大小。
void*万能接收地址,但是不能直接解引用和加减。- 指针三种合法运算:指针 ± 整数、指针 - 指针、指针关系比较。指针相减、比较,仅限同一块连续内存。
注意:
- 不要混淆指针变量本身的地址
&ps,和指针存储的地址ps;- 不要对不相关的两个指针做相减、大小比较,行为未定义;
- void * 使用前,必须强制转换为对应类型,再做解引用;
- 指针 + 1 不是 + 1 字节,是加上指向类型的 sizeof 大小
8481



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



