本文适合希望从零系统掌握 C 指针的学习者。 文章将由浅入深,从地址与变量,到函数指针、数组指针、回调函数与
qsort实现。
在计算机中,内存就像一栋有门牌号的宿舍楼。每个“房间”(字节)都有编号(地址),CPU 通过地址快速访问数据。
#include <stdio.h>
int main()
{
int a = 10;
printf("a 的地址是:%p\n", &a);
return 0;
}输出类似:
a 的地址是:006FFD70💡 结论:
变量,地址是指针的值。& 操作符用于取地址,* 操作符用于解引用访问地址中的内容。指针变量是用来存放地址的变量。
int a = 10;
int *pa = &a; // pa 保存 a 的地址
*pa = 0; // 解引用,修改 a 的值📘 要点:
int * 每次操作 4 字节,char * 每次操作 1 字节;int *指针大小为 4 字节,64 位为 8 字节。int n = 0x11223344;
int *pi = &n;
char *pc = (char *)&n;
*pi = 0; // 修改全部字节
*pc = 0; // 仅修改最低字节🧭 结论:
p+1、p-1)时的跳跃长度依据。数组名在大多数情况下等价于首元素地址。
int arr[5] = {1,2,3,4,5};
int *p = arr;
printf("%d %d\n", arr[2], *(p+2)); // 输出相同🔍 例外情况:
sizeof(arr):得到整个数组大小;也就是“整个数组”的字节数。&arr:表示整个数组的地址(与 arr 类型不同,其类型升级成数组指针)。当数组作为函数参数传递时,实际上传递的是 首元素的地址。
void test(int arr[]) // 实际是指针
{
printf("%d\n", sizeof(arr));
} ⚠️ 注意:
在函数内部无法用 sizeof 得到数组总大小:
1、数组名是数组⾸元素的地址;那么在数组传参的时候,传递的是数组名,也就是说本质上数组传参传递的是数组⾸元素的地址。 2、所以函数形参的部分理论上应该使⽤指针变量来接收⾸元素的地址。 3、那么在函数内部我们写sizeof(arr) 计算的是⼀个地址的⼤⼩(单位字节)⽽不是数组的⼤⼩(单位字节)。正是因为函数的参数部分是本质是指针,所以在函数内部是没办法求的数组元素个数的。
#include <stdio.h>
int main()
{
char ch = 'w';
char *pc = &ch;
*pc = 'W';
printf("%c\n", ch);
return 0;
}pc 是一个指向 char 类型的指针,存放 ch 的地址。*pc 可修改 ch 的值。const char *pstr = "hello bit.";
printf("%s\n", pstr);⚠️ 注意:
pstr 并不是存放字符串本身,而是存放字符串首字符的地址。int *p1[10]; // 指针数组
int (*p2)[10]; // 数组指针p1 是数组,每个元素是 int*p2 是指针,指向一个含 10 个 int 的数组。int *p1[10]: 先结合 p1[10] → 这是一个长度 10 的数组,再拿 int * 修饰元素类型 → 元素是指向 int 的指针。 读作:“p1 是含 10 个 int * 的数组”。 int (*p2)[10]; 括号强制 *p2 先结合 → p2 是一个指针,再下标 [10] 说明它指向“长度 10 的 int 数组”。 读作:“p2 是指向 int[10] 这一整个数组的指针”。
int arr[10] = {0};
int (*p)[10] = &arr;#include <stdio.h>
void print2D(int (*p)[5], int r, int c)
{
for(int i = 0; i < r; i++) {
for(int j = 0; j < c; j++) {
printf("%d ", *(*(p+i)+j));
}
printf("\n");
}
}
int main()
{
int arr[3][5] = {{1,2,3,4,5}, {2,3,4,5,6}, {3,4,5,6,7}};
print2D(arr, 3, 5);
}int (*p)[5] 是“指向含 5 个 int 的一行”的指针,加 1 跳过 20 B;int Add(int x, int y) { return x + y; }
int main()
{
int (*pf)(int, int) = Add;
printf("%d\n", pf(2, 3));
return 0;
}📘 函数名即地址,可直接赋给函数指针。
写法 | 类型 | 含义 |
|---|---|---|
int Add(int,int) | 函数类型 | 只在声明/定义处出现 |
&Add | int (*)(int,int) | 显式取地址,得到函数指针 |
Add | 退化后同样为 int (*)(int,int) | 隐式退化 |
因此三条初始化语句完全等价:
int (*pf)(int, int) = Add; // 最常用
int (*pf)(int, int) = &Add; // 等价
int (*pf)(int, int) = ******Add; // 任意层 * 也等价,因为函数名先退化#include <stdio.h>
int add(int a,int b){return a+b;}
int sub(int a,int b){return a-b;}
int mul(int a,int b){return a*b;}
int divi(int a,int b){return a/b;}
int main()
{
int (*ops[5])(int,int) = {0,add,sub,mul,divi};
int choice, x, y;
while(1){
printf("\n1:add 2:sub 3:mul 4:div 0:exit\nChoose: ");
scanf("%d",&choice);
if(choice==0) break;
printf("输入操作数:");
scanf("%d%d",&x,&y);
printf("结果: %d\n", ops[choice](x,y));
}
}✅ 使用函数指针数组可快速实现函数跳转逻辑,省去冗余的switch。
#include <stdio.h>
#include <stdlib.h>
int cmp_int(const void *a, const void *b)
{
return *(int*)a - *(int*)b;
}
int main()
{
int arr[] = {9,3,7,1,5};
qsort(arr, 5, sizeof(int), cmp_int);
for(int i=0; i<5; i++)
printf("%d ", arr[i]);
}qsort 的最后一个参数是函数指针,由用户提供比较逻辑。 函数地址可作为参数传入另一个函数,由被调用函数在合适时“回调”。
其中主要实现的是qsort函数的最后一个参数,后续结构体排序时也是做好类型转换,写好比较逻辑。
#include <stdio.h>
#include <string.h>
void swap(void *p1, void *p2, int size)
{
for(int i=0; i<size; i++) {
char tmp = *((char*)p1+i);
*((char*)p1+i) = *((char*)p2+i);
*((char*)p2+i) = tmp;
}
}
void bubble(void *base, int count, int size, int(*cmp)(void*,void*))
{
for(int i=0;i<count-1;i++)
for(int j=0;j<count-i-1;j++){
char *p1=(char*)base+j*size;
char *p2=(char*)base+(j+1)*size;
if(cmp(p1,p2)>0) swap(p1,p2,size);
}
}
int cmp_int(void *a, void *b){ return *(int*)a - *(int*)b; }
int main()
{
int arr[]={4,1,3,9,2};
bubble(arr,5,sizeof(int),cmp_int);
for(int i=0;i<5;i++) printf("%d ",arr[i]);
}sizeof | strlen |
|---|---|
1. sizeof是操作符2. sizeof计算操作数所占内存的⼤⼩,单位是字节3. 不关注内存中存放什么数据 | 1. strlen是库函数,使⽤需要包含头⽂件 string.h2. srtlen是求字符串⻓度的,统计的是 \0 之前字符的隔个数3. 关注内存中是否有 \0 ,如果没有 \0 ,就会持续往后找,可能会越界 |
char arr1[3] = {'a','b','c'};
char arr2[] = "abc";
printf("%d %d\n", sizeof(arr1), strlen(arr2));指针是 C 语言的灵魂。掌握指针不仅能写出更高效的程序,也能理解底层内存的运作原理。 建议读者:
qsort 与函数回调。