C语言 - 指针进阶
1. 数组和指针
1.1 区分数组和指针
数组是一组相同类型元素的集合,而指针实际上是一个存储地址的变量
#define _CRT_SECURE_NO_WARNINGS #include <stdio.h> int main() { // 数组arr int arr[] = { 1,2,3,4,5 }; int a = 0; // 指针变量 pa int* pa = &a; }
如例,数组arr是一组整数的集合,指针pa是一个存储整形变量a地址的变量
数组和指针是两个不同的概念,它们之间有什么联系?
1.2 数组和指针的关联
数组和指针之间唯一的联系是,数组名 = 数组首元素的地址,这个地址可以用指针进行存储
#define _CRT_SECURE_NO_WARNINGS #include <stdio.h> int main() { // 数组arr int arr[] = { 1,2,3,4,5 }; int* pa = arr; printf("%p %p %p\n",&arr[0], arr, pa); }
如图,&arr[0] = arr ,数组首元素地址 = 数组名
在这个例子中,数组首元素的类型是int,所以可以用整形指针pa存储数组名所表示的数组首元素地址
2.1 指针数组
2.1 指针数组的概念和写法
指针数组是存储一组指针的数组,写法如下
#define _CRT_SECURE_NO_WARNINGS #include <stdio.h> int main() { int a, b, c; // 指针数组arr int* arr[] = { &a,&b,&c }; return 0; }
arr[]表示数组,int*表示数组每一个元素的类型是整形指针
2.2 指针数组的使用
使用指针数组模拟一个二维数组
#define _CRT_SECURE_NO_WARNINGS #include <stdio.h> int main() { int arr1[] = { 1,2,3,4,5 }; int arr2[] = { 2,3,4,5,6 }; int arr3[] = { 3,4,5,6,7 }; int* arr[] = { &arr1,&arr2,&arr3 }; int i = 0; for (i = 0; i < 3; i++) { int j = 0; for (j = 0; j < 5; j++) { printf("%d ",arr[i][j]); } printf("\n"); } }
3. 数组指针
3.1 数组名 VS &数组名
数组名等于数组首元素的地址,&数组名等于什么?
#define _CRT_SECURE_NO_WARNINGS #include <stdio.h> int main() { int arr[10]; printf("%p %p\n", arr, arr + 1); printf("%p %p\n", &arr, &arr + 1); }
如图,arr +1,跳过4个字节,而 &arr + 1,跳过了40个字节
因为指针步长由指针类型决定,所以 &arr 取出的是整个数组的地址
3.2 数组指针的概念和写法
&arr 取出的数组地址可以由变量存储,这个变量叫做数组指针
数组指针,写法如下
#define _CRT_SECURE_NO_WARNINGS #include <stdio.h> int main() { int arr[10]; // 数组指针 int(*pa)[10] = &arr; }
(*pa) 表示pa是一个指针
[10] 表示pa存储的是一个数组的地址,且数组存放10个数据
int 表示数组每一个数据的类型是整型
3.3 二维数组数组名等于什么
已知,一维数组数组名 = 数组首元素的地址,&一维数组数组名 = 数组的地址,二维数组数组名等于什么?
二维数组数组名 = 二维数组 第一行 一维数组的地址
如图,二维数组arr + 1,直接从第一行跳到了第二行
3.4 数组指针的使用
数组指针通常用于二维数组传参,如下例
#define _CRT_SECURE_NO_WARNINGS #include <stdio.h> void print(int(*pa)[5], int row, int col) { int i = 0; for (i = 0; i < row; i++) { int j = 0; for (j = 0; j < col; j++) { printf("%d ", pa[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} }; print(arr, 3, 5); }
二维数组数组名 = 二维数组 第一行 一维数组的地址,所以形参用 数组指针 int(*pa)[5] 来接收
4. 数组传参,指针传参
总结数组,指针传参,函数参数如何设计
4.1 一维数组传参
#define _CRT_SECURE_NO_WARNINGS #include <stdio.h> void test(int arr[])//ok? 对 - 数组传参 形参可以写成数组形式 且因为arr本质上传递的是首元素地址,所以可以不指定数组大小 {} void test(int arr[10])//ok? 对 {} void test(int* arr)//ok? 对 - arr传递数组arr首元素地址 所以形参可以写成整形指针 {} void test2(int* arr[20])//ok? 对 - 数组传参 形参可以写成数组形式 {} void test2(int** arr)//ok? 对 - 数组arr2每个元素类型是int*整形指针,所以数组名arr2 = &整形指针int* = 二级指针,形参用int**二级指针接收 {} int main() { int arr[10] = { 0 }; int* arr2[20] = { 0 }; test(arr); test2(arr2); }
4.2 二维数组传参
#define _CRT_SECURE_NO_WARNINGS #include <stdio.h> void test(int arr[3][5])//ok? 对 {} void test(int arr[][])//ok?错 - 二维数组的行可以省略,列不能省略 {} void test(int arr[][5])//ok? 对 {} void test(int* arr)//ok?错 - 二维数组数组名 = 二维数组 第一行 一维数组的地址 是一个数组指针 而这里形参arr是整形指针int* {} void test(int* arr[5])//ok?错 - 这里的形参arr是个指针数组 {} void test(int(*arr)[5])//ok?对 - arr是数组指针 指针指向存放5个元素的数组 数组每个元素类型是整形int {} void test(int** arr)//ok?错 - 形参arr 是个二级指针 {} int main() { int arr[3][5] = { 0 }; test(arr); }
4.3 一级指针传参
当一个函数的参数部分为一级指针的时候,函数能接收什么参数?
#define _CRT_SECURE_NO_WARNINGS #include <stdio.h> // 一级指针传参,用一级指针p接收 void print(int* p) { } int main() { int a = 0; int arr[10] = { 1,2,3,4,5,6,7,8,9 }; int* p = arr; // 可以传整形变量地址 print(&a); // 传一级指针 print(p); // 传数组名 print(arr); return 0; }
4.4 二级指针传参
当函数的参数为二级指针的时候,可以接收什么参数?
#define _CRT_SECURE_NO_WARNINGS #include <stdio.h> void test(char** p) { } int main() { char c = 'b'; char* pc = &c; char** ppc = &pc; char* arr[10]; // 可以传 &一级字符指针pc test(&pc); // 可以传 二级字符指针 test(ppc); // 可以传 每个元素类型为char*字符指针的arr数组名 test(arr); return 0; }
5. 函数指针
5.1 函数指针的概念和写法
函数指针与数组指针类型,是一个存储函数地址的变量
写法如下
#define _CRT_SECURE_NO_WARNINGS #include <stdio.h> int add(int x, int y) { return x + y; } int main() { // 函数指针pa,pb int (*pa)(int, int) = add; int (*pb)(int, int) = &add; }
(*pa) 表示pa是一个指针
(int, int)表示pa存储的是一个函数的地址,函数有两个类型为整形的形参
(*pa)前面的int,表示函数的返回类型为int
需要注意的是,在函数指针中,&函数名 = 函数名 = 函数的地址
5.2 函数指针的使用
#define _CRT_SECURE_NO_WARNINGS #include <stdio.h> int add(int x, int y) { return x + y; } int main() { int (*pa)(int, int) = add; int ret = (*pa)(1, 2); printf("%d\n", ret); }
6. 函数指针数组
6.1 函数指针数组的概念和写法
函数指针数组是存放函数地址的数组
写法如下
#define _CRT_SECURE_NO_WARNINGS #include <stdio.h> int add(int x, int y) { return x + y; } int sub(int x, int y) { return x - y; } int main() { int(*ptr[2])(int,int) = { add,sub }; }
int(*ptr[2])(int,int) 如何理解?
ptr首先会与[2]结合,表示ptr是一个数组,数组有2个元素
int (* ptr[2] )(int,int) ---> int (*)(int,int)
(*) (int,int)表示ptr数组的每一个元素是函数地址,函数有两个整形类型形参
6.2 函数指针数组的使用
#include <stdio.h> int add(int x, int y) { return x + y; } int sub(int x, int y) { return x - y; } int mul(int x, int y) { return x * y; } int div(int x, int y) { return x / y; } void menu() { printf("1.add 2.sub\n"); printf("3.mul 4.div\n"); } int main() { int(*ptr[5])(int, int) = { 0,add,sub,mul,div }; //函数指针数组 int input = 0; int x = 0; int y = 0; int ret = 0; do { menu(); printf("输入: "); scanf("%d", &input); if (input > 4 || input <= 0) { printf("退出程序\n"); } else { printf("输入x: "); scanf("%d", &x); printf("输入y: "); scanf("%d", &y); ret = (ptr[input])(x, y); //通过input,锁定函数地址,最后传参得到结果 printf("%d\n", ret); } } while (input); }
7. 回调函数
7.1 什么是回调函数
回调函数就是一个通过函数指针调用的函数。如果你把函数的地址作为参数传递给另一个
函数,当这个指针被用来调用其所指向的函数时,我们就说这是回调函数
下面举例说明
#define _CRT_SECURE_NO_WARNINGS #include <stdio.h> int Add(int x, int y) { return x + y; } void calc(int(*pc)(int, int)) { int x = 0; int y = 0; int ret = 0; printf("请输入2个操作数:"); scanf("%d %d", &x, &y); ret = pc(x, y); printf("%d\n", ret); } int main() { calc(Add); }
将Add函数地址作为参数传递给函数calc,当函数指针pc(Add函数地址)在calc函数内部使用调用Add函数时,这时候的Add函数就可以称为回调函数
7.2 回调函数的应用
#include <stdio.h> int cmp_int(const void* e1, const void* e2) { return * (int*)e1 - * (int*)e2; } struct Stu { char name[20]; int age; }; // 以名字排序 int cmp_struct_name(const void* e1, const void* e2) { return strcmp(((struct Stu*)e1)->name, ((struct Stu*)e2)->name); } // 以年龄排序 int cmp_struct_age(const void* e1, const void* e2) { return ((struct Stu*)e1)->age - ((struct Stu*)e2)->age; } void swap(char* s1, char* s2, size_t width) { size_t i = 0; for (i = 0; i < width; i++) { char tmp = *s1; *s1 = *s2; *s2 = tmp; s1++; s2++; } } void bubble_sort(void* base, size_t sz, size_t width, int (*cmp)(const void* e1, const void* e2)) { size_t i = 0; for (i = 0; i < sz - 1; i++) { size_t j = 0; for (j = 0; j < sz - 1 - i; j++) { if (cmp( (char*)base + (j * width), (char*)base + (j+1)*width ) > 0) { swap((char*)base + (j * width), (char*)base + (j + 1) * width, width); } } } } // 排序整形 void sort_int() { int arr[] = { 9,8,7,6,5,4,3,2,1,0 }; int sz = sizeof(arr) / sizeof(arr[0]); bubble_sort(arr,sz,sizeof(arr[0]), cmp_int); for (int i = 0; i < sz; i++) { printf("%d ", arr[i]); } } // 排序结构体 void sort_struct() { struct Stu s[5] = { {"b",25},{"e",20},{"a",18},{"c",30},{"d",12} }; int sz = sizeof(s) / sizeof(s[0]); //bubble_sort(s, sz, sizeof(s[0]), cmp_struct_name); bubble_sort(s, sz, sizeof(s[0]), cmp_struct_age); } int main() { //sort_int(); sort_struct(); }
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· winform 绘制太阳,地球,月球 运作规律
· AI与.NET技术实操系列(五):向量存储与相似性搜索在 .NET 中的实现
· 超详细:普通电脑也行Windows部署deepseek R1训练数据并当服务器共享给他人
· 【硬核科普】Trae如何「偷看」你的代码?零基础破解AI编程运行原理
· 上周热点回顾(3.3-3.9)