Category 世界杯乳神

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

本文适合希望从零系统掌握 C 指针的学习者。

文章将由浅入深,从地址与变量,到函数指针、数组指针、回调函数与 qsort 实现。

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

代码语言:javascript复制#include

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+1、p-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

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

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

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

#include

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

#include

void swap(void *p1, void *p2, int size)

{

for(int i=0; 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

for(int j=0;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 与函数回调。模拟标准库函数培养底层思维。

Copyright © 2088 世界杯名额_世界杯结果 - tylzr.com All Rights Reserved.
友情链接