尧图网络科技YAOTU DIGITAL 获取报价
获取报价
首页 / 资讯中心 / 文章详情

理解 C 语言指针:从基础到函数指针

发布时间:2026/9/28 20:43:13

资讯中心
01
ARTICLE

理解 C 语言指针:从基础到函数指针

理解 C 语言指针:从基础到函数指针
前言很多人觉得指针难但指针的本质就一句话指针就是内存地址。搞懂这个剩下所有花样都是围绕这个地址指向什么类型、加一减一怎么跳展开的。这篇把我学指针的几个关键突破点整理出来。一、指针是什么内存就像是一栋楼每栋楼都有许多门牌号通过门牌号就能找到你家也就是地址。int a 10; int* p a; // 取地址p 存 a 的地址 printf(%d\n, *p); // * 解引用通过地址拿到 a 的值 10不同的指针类型决定着解引用读几个字节加一减一跳过几个字节如int*就会跳过4个字节char*就会跳过1个字节。二、指针运算指针 ± 整数指针加 1跳过的是一个元素的大小不是 1 字节。int arr[5] {1,2,3,4,5}; int* p arr; // 指向 arr[0] p 1; // 指向 arr[1]地址加 4 字节而不是 1 字节指针 - 指针两个指针相减得到它们之间相隔的元素个数。前提是它们指向同一块连续空间通常同一数组内因为内存地址本质上是一串16进制的数字俩个数相减就能得出它们之间差了多少个数。int arr[5] {1,2,3,4,5}; int* p1 arr[1]; // 指向第 2 个 int* p2 arr[4]; // 指向第 5 个 int n p2 - p1; // 3相隔 3 个元素指针的关系运算同一数组内可以用 和 比较两个元素位置的前后。注意指针不能相加、不能乘除这些没有意义。三、野指针野指针就是指向位置不可知的指针主要三种成因未初始化就使用int* p; *p 10;p 的值是随机的因为天知道p指向了哪。越界访问访问数组范围之外的内存。指针指向的空间被释放后没有置空学动态内存时会遇到。规避方法定义时就初始化暂时没有指向就设为NULL、释放后置NULL、使用前先判断非空、绝不过界访问。四、const 修饰指针关键看const在*的左边还是右边int a 10; const int* p1 a; // const 在 * 左边*p1指向的内容不能改 int* const p2 a; // const 在 * 右边p2指针本身不能改 const int* const p3 a; // 都不能改 // p1 b; // 可以p1 本身能指向别的 // *p1 20; // 不行内容不能改 // p2 b; // 不行p2 不能再指向别的 // *p2 20; // 可以口诀const 在*左边修饰内容*pconst 在*右边修饰指针本身p反正就是*p是指向的内容因为有解引用符p就是指针本身存的地址。五、数组名的理解数组名绝大多数情况下就是首元素的地址但有两个例外sizeof(数组名)表示整个数组算整个数组的字节大小。数组名取整个数组的地址。最容易搞错的是arr、arr[0]、arr三者值相同、类型不同加一步长就不同int arr[5] {0}; arr 1; // 加 4 字节跳过一个 int arr 1; // 加 20 字节跳过整个数组arr的类型是int(*)[5]指向含 5 个 int 的数组所以加一就是跳一整个数组。六、指针访问数组核心等价关系p[i] *(p i) arr[i] *(arr i)[]下标运算本质就是首地址 偏移量再解引用。七、数组传参的本质数组传参传的是首元素地址所以形参int arr[]和int* arr完全等价函数里的arr是指针void test(int arr[]) // 本质是 int* arr { printf(%d\n, sizeof(arr)); // 指针大小不是数组大小 }所以函数内部没法用sizeof求元素个数必须把长度作为另一个参数一起传进去。二维数组同理它的首元素是第一行一个一维数组指针形式是int (*p)[5]访问第 i 行第 j 列就是*(*(pi)j)。八、二级指针指针变量也是变量也有地址。存指针变量的地址的就是二级指针。int a 10; int* p a; int** pp p; *pp b; // 等价于 p b **pp 30; // 等价于 a 30记忆链条pp存p的地址p存a的地址像套娃一样。九、指针数组 vs 数组指针靠运算符优先级区分int* p1[10]; // 指针数组[] 优先级高p1 是数组元素是 int* int (*p2)[10]; // 数组指针括号让 * 先结合p2 是指针指向 int[10]一句话指针数组是存指针的数组数组指针是指向数组的指针。十、字符指针字符指针指向字符串字面量时存的是字符串首字符的地址不是整个字符串const char* pstr hello; // 存的是 h 的地址经典区别用同一个字符串字面量初始化两个数组会开辟两块不同内存而让两个指针指向同一个字符串常量它们指向同一块内存。十一、函数指针函数也有地址函数名就是函数的地址。存函数地址的变量是函数指针int add(int x, int y) { return x y; } int (*pf)(int, int) add; printf(%d\n, pf(2, 3)); // 通过函数指针调用*和变量名必须用括号包起来否则就成了返回指针的函数。十二、函数指针数组既然能把函数地址存进指针那也能把多个函数地址存进数组——这就是函数指针数组int (*parr[3])(int, int) { add, sub, mul }; // parr 是数组元素类型是 int(*)(int, int) printf(%d\n, parr[0](10, 5)); // 调用 add解析parr先和[3]结合成数组数组的每个元素是int(*)(int,int)这种函数指针。十三、转移表函数指针数组最典型的应用就是转移表用下标直接跳到对应函数替代又长又重复的switch。以计算器为例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 div(int a, int b) { return a / b; } int main() { int x, y, input 1, ret 0; // 下标 0 放空1~4 对应四个运算函数 int (*p[5])(int, int) { 0, add, sub, mul, div }; do { printf(1:add 2:sub 3:mul 4:div 0:exit\n); printf(请选择); scanf(%d, amp;input); if (input amp;gt; 1 amp;amp;amp;amp; input amp;lt; 4) { printf(输入操作数); scanf(%d %d, amp;amp;x, amp;amp;y); ret (*p[input])(x, y); // 用 input 当下标直接跳转 printf(ret %d\n, ret); } else if (input 0) printf(退出\n); else printf(输入有误\n); } while (input); return 0; }原来要写四段几乎一样的case现在一次(*p[input])(x, y)就搞定代码短了、也更好扩展。十四、回调函数把函数指针当参数传给另一个函数那个被调用的函数就是回调函数。它最大的价值是解耦——调用方不用知道具体调的是谁。C 标准库的qsort就是最典型的例子下面详细拆解。十五、qsort 的使用在模拟实现之前先看标准库的qsort怎么用。它声明在stdlib.h中原型如下void qsort(void *base, size_t nmemb, size_t size, int (*compar)(const void *, const void *));四个参数的含义分别是base是待排序数组的首地址nmemb是元素个数size是单个元素的字节大小最后一个是比较函数指针。排序结果会直接作用在原数组上。比较函数返回int规则和手写排序里的比较逻辑一样返回负数表示第一个参数排在前面返回正数表示第二个参数排在前面返回 0 表示相等。以常见的int数组升序为例#include stdio.h #include stdlib.h int int_cmp(const void* p1, const void* p2) { return (int)p1 - (int)p2; // 升序p1 - p2 } int main() { int arr[] {1,3,5,7,9,2,4,6,8,0}; int n sizeof(arr) / sizeof(arr[0]); qsort(arr, n, sizeof(int), int_cmp); for (int i 0; i lt; n; i) printf(%d , arr[i]); return 0; }如果想让int数组降序只要把比较函数的返回值反过来就行return *(int*)p2 - *(int*)p1;。对于浮点数、结构体等类型则不要直接相减要通过分支返回明确的 1、-1 或 0避免小数被截断。十六、qsort 的模拟实现重点qsort是通用排序函数它不知道你要排什么类型靠三样东西做到通用void* base不假设类型、size每个元素几字节、比较函数指针把怎么比较交给使用者。第一步void*是什么void*是通用指针能接收任何类型的地址但不能直接解引用、不能直接加减编译器不知道指向的数据多大。要用它必须先强转成具体类型。第二步怎么定位第 j 个元素因为char恰好占 1 字节所以把base强转成char*后 j * size就精确地前进了 j 个元素(char*)base j * size // 第 j 个元素的起始地址第三步怎么交换任意类型的数据不知道元素多大就一个字节一个字节地交换共交换size次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; } }第四步通用冒泡排序外层内层循环和普通冒泡一样只是比较交给回调函数、交换交给_swapvoid bubble(void* base, int count, int size, int (*cmp)(const void*, const void*)) { for (int i 0; i count - 1; i) { for (int j 0; j count - i - 1; j) { // 第 j 个元素地址 (char*)base j*size if (cmp((char*)base j*size, (char*)base (j1)*size) 0) _swap((char*)base j*size, (char*)base (j1)*size, size); } } }第五步使用和真 qsort 一样int int_cmp(const void* p1, const void* p2) { return *(int*)p1 - *(int*)p2; // 升序p1 - p2 } int arr[] {1,3,5,7,9,2,4,6,8,0}; bubble(arr, sizeof(arr)/sizeof(arr[0]), sizeof(int), int_cmp);整个链条就是bubble用void*size做到什么类型都能排比较函数用回调交给使用者。注意比较函数返回int所以浮点数不能直接用相减小数会被截断成 0要写成if/else返回 1、-1、0。写在最后指针是一套环环相扣的逻辑数组名是地址 → 传参传的是地址 → 数组指针指向一行 → 函数指针指向函数 → 函数指针数组组成转移表 → 回调函数把函数指针当参数 → qsort 用 void* 做到通用。把这根链条理清指针就不再可怕反而成了很强的工具。
02
RELATED NEWS

相关资讯

更多网站建设与数字化升级内容

03
WHY YAOTU

想打造同款高转化官网?

懂行业、懂生意,从建站到增长一站式陪跑

◈

场景化定制

不做模板站,围绕你的业务场景量身设计,小众不撞款。

◐

营销型架构

以转化目标组织内容与路径,让官网真正带来询盘。

▲

全周期服务

设计、开发、运营、运维一体,上线只是开始。

免费获取你的建站方案

留下需求,专属顾问 24 小时内为你输出方案建议。