首页
学习
活动
专区
圈层
工具
发布
社区首页 >专栏 >零基础到精通:C 指针全景指南

零基础到精通:C 指针全景指南

作者头像
Extreme35
发布2025-12-23 18:04:01
发布2025-12-23 18:04:01
110
举报
文章被收录于专栏:DLDL

C语言指针从入门到精通(含源码示例)

本文适合希望从零系统掌握 C 指针的学习者。 文章将由浅入深,从地址与变量,到函数指针、数组指针、回调函数与 qsort 实现。



一、内存、地址与指针的本质

在计算机中,内存就像一栋有门牌号的宿舍楼。每个“房间”(字节)都有编号(地址),CPU 通过地址快速访问数据。

代码语言:javascript
复制
#include <stdio.h>
int main() 
{
    int a = 10;
    printf("a 的地址是:%p\n", &a);
    return 0;
}

输出类似:

代码语言:javascript
复制
a 的地址是:006FFD70

💡 结论:

  • 地址即内存单元的编号;
  • 在 C 语言中,指针是保存地址的变量,地址是指针的
  • & 操作符用于取地址,* 操作符用于解引用访问地址中的内容。

二、指针变量与类型

指针变量是用来存放地址的变量。

代码语言:javascript
复制
int a = 10;
int *pa = &a;   // pa 保存 a 的地址
*pa = 0;        // 解引用,修改 a 的值

📘 要点:

  • 指针类型决定了解引用时访问的字节数;
  • int * 每次操作 4 字节,char * 每次操作 1 字节;
  • 32 位系统下int *指针大小为 4 字节,64 位为 8 字节。

三、指针类型的意义

代码语言:javascript
复制
int n = 0x11223344;
int *pi = &n;
char *pc = (char *)&n;

*pi = 0;   // 修改全部字节
*pc = 0;   // 仅修改最低字节

🧭 结论:

  • 指针类型决定了解引用时“访问的步长”;
  • 也是指针运算(p+1p-1)时的跳跃长度依据。

四、指针与数组

数组名在大多数情况下等价于首元素地址。

代码语言:javascript
复制
int arr[5] = {1,2,3,4,5};
int *p = arr;
printf("%d %d\n", arr[2], *(p+2)); // 输出相同

🔍 例外情况:

  • sizeof(arr):得到整个数组大小;也就是“整个数组”的字节数。
  • &arr:表示整个数组的地址(与 arr 类型不同,其类型升级成数组指针)。

五、指针与函数参数传递

当数组作为函数参数传递时,实际上传递的是 首元素的地址

代码语言:javascript
复制
void test(int arr[]) // 实际是指针
{ 
	printf("%d\n", sizeof(arr)); 
} 

⚠️ 注意: 在函数内部无法用 sizeof 得到数组总大小:

1、数组名是数组⾸元素的地址;那么在数组传参的时候,传递的是数组名,也就是说本质上数组传参传递的是数组⾸元素的地址。 2、所以函数形参的部分理论上应该使⽤指针变量来接收⾸元素的地址。 3、那么在函数内部我们写sizeof(arr) 计算的是⼀个地址的⼤⼩(单位字节)⽽不是数组的⼤⼩(单位字节)。正是因为函数的参数部分是本质是指针,所以在函数内部是没办法求的数组元素个数的。


六、字符指针的本质

代码语言:javascript
复制
#include <stdio.h>
int main()
{
    char ch = 'w';
    char *pc = &ch;
    *pc = 'W';
    printf("%c\n", ch);
    return 0;
}
说明
  • pc 是一个指向 char 类型的指针,存放 ch 的地址。
  • 通过 *pc 可修改 ch 的值。
字符串常量指针
代码语言:javascript
复制
const char *pstr = "hello bit.";
printf("%s\n", pstr);

⚠️ 注意:

  • pstr 并不是存放字符串本身,而是存放字符串首字符的地址
  • 字符串常量一般在只读存储区,不能被修改。

七、数组指针与二维数组传参

代码语言:javascript
复制
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] 这一整个数组的指针”。

初始化
代码语言:javascript
复制
int arr[10] = {0};
int (*p)[10] = &arr;
二维数组传参
代码语言:javascript
复制
#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);
}
代码语言:javascript
复制
int (*p)[5] 是“指向含 5 个 int 的一行”的指针,加 1 跳过 20 B;

八、函数指针与回调机制

代码语言:javascript
复制
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)

隐式退化

因此三条初始化语句完全等价:

代码语言:javascript
复制
int (*pf)(int, int) = Add;      // 最常用
int (*pf)(int, int) = &Add;     // 等价
int (*pf)(int, int) = ******Add; // 任意层 * 也等价,因为函数名先退化

九、函数指针数组与转移表

代码语言:javascript
复制
#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。


十、回调函数与 qsort 示例

代码语言:javascript
复制
#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

其中主要实现的是qsort函数的最后一个参数,后续结构体排序时也是做好类型转换,写好比较逻辑。

代码语言:javascript
复制
#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 区别

sizeof

strlen

1. sizeof是操作符2. sizeof计算操作数所占内存的⼤⼩,单位是字节3. 不关注内存中存放什么数据

1. strlen是库函数,使⽤需要包含头⽂件 string.h2. srtlen是求字符串⻓度的,统计的是 \0 之前字符的隔个数3. 关注内存中是否有 \0 ,如果没有 \0 ,就会持续往后找,可能会越界

代码语言:javascript
复制
char arr1[3] = {'a','b','c'};
char arr2[] = "abc";
printf("%d %d\n", sizeof(arr1), strlen(arr2));

十二、结语

指针是 C 语言的灵魂。掌握指针不仅能写出更高效的程序,也能理解底层内存的运作原理。 建议读者:

  • 理解地址、类型与内存模型;
  • 学会用函数指针与回调函数提高代码通用性;
  • 使用调试器观察地址变化;
  • 尝试自己实现 qsort 与函数回调。
  • 模拟标准库函数培养底层思维。

本文参与 腾讯云自媒体同步曝光计划,分享自作者个人站点/博客。
原始发表:2025-10-14,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • C语言指针从入门到精通(含源码示例)
    • 一、内存、地址与指针的本质
    • 二、指针变量与类型
    • 三、指针类型的意义
    • 四、指针与数组
    • 五、指针与函数参数传递
    • 六、字符指针的本质
      • 说明
      • 字符串常量指针
    • 七、数组指针与二维数组传参
      • 初始化
      • 二维数组传参
    • 八、函数指针与回调机制
      • 类型系统:函数指针的声明与初始化
    • 九、函数指针数组与转移表
    • 十、回调函数与 qsort 示例
      • 模拟实现 qsort
    • 十一、sizeof 与 strlen 区别
    • 十二、结语
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档