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();
}

posted @   qyx1  阅读(36)  评论(0编辑  收藏  举报  
相关博文:
阅读排行:
· winform 绘制太阳,地球,月球 运作规律
· AI与.NET技术实操系列(五):向量存储与相似性搜索在 .NET 中的实现
· 超详细:普通电脑也行Windows部署deepseek R1训练数据并当服务器共享给他人
· 【硬核科普】Trae如何「偷看」你的代码?零基础破解AI编程运行原理
· 上周热点回顾(3.3-3.9)
点击右上角即可分享
微信分享提示