指针作为c语言学习中最重要的一部分之一,能否掌握好指针是我们c学习的如何的重要指标,在经过一段时间的学习后,我在这篇博客将我对于指针的进阶用法的学习进行一定的分享。
- 【c语言进阶】指针进阶修炼手册
- 0、前期准备
- 1、字符指针
- 2、指针数组
- 3、数组指针
- 3.1数组指针的定义
- 3.2&数组名和数组名的区别
- 3.3数组指针的使用
- 4、数组传参和指针传参
- 4.1一维数组传参
- 4.2二维数组传参
- 4.3一级指针、二级指针传参
- 一级指针传参
- 二级指针传参
- 5、函数指针
- 函数指针实现简易计算器
- 6、函数指针数组
- 函数指针数组实现简易计算器
- 7、指向函数指针数组的指针
- 8、回调函数
- 9、结语
在进入正式的指针进阶的学习时,我们首先简单回顾一下基本的指针相关的知识,感觉看不明白的记得先去补基础哦。
指针的基本概念:
1、指针就是地址,我们平常所说的指针是指指针变量,指针变量是用来存放地址的,存放的地址唯一标识了一块内存空间。
2、指针的大小固定为4/8字节(32位平台/64位平台)。
32位平台的地址占4个字节,64位平台的地址占8个字节。
3、指针有类型,指针的类型决定了指针进行±整数的步长,如char*类型的±会指向下一个字节的地址,也决定了指针解引用操作的时候的权限。
4、指针的运算。
1、字符指针int main(void)
{
char ch = 'w';
char* pc = &ch;
*pc = 'e';
return 0;
} //像这种对于字符指针的使用应该是我们最常遇到的。
但另一种用法我们同样需要清楚
int main(void)
{
const char* ch="solayiyixuanxuan";
printf("%s",ch);
return 0;
}//这种用法如何?在下方我会对该用法进行解释:
一个字符指针却拿了一整个字符串进行初始化?那么字符指针里面存储了谁的地址呢?
通过监视窗口我们可以很清楚的看到,指针内存储了字符串的首个字符的地址。
这种类型的指针我们没有办法对其进行解引用(忽略掉const),这是因为该字符串位于内存中的常量区,我们不能对其进行修改。
接下来我们来通过一道题目来进一步了解
#includeint main() { char str1[] = "hello bit."; char str2[] = "hello bit."; const char *str3 = "hello bit."; const char *str4 = "hello bit."; if(str1 ==str2) printf("str1 and str2 are samen"); else printf("str1 and str2 are not samen"); if(str3 ==str4) printf("str3 and str4 are samen"); else printf("str3 and str4 are not samen"); return 0; }
这道题目的输入结果是:
str1 and str2 are not same
str3 and str4 are same
为什么是这种结果?str1和str2是字符数组的数组名,是字符串的首地址,虽然两个字符串内容相同,但处于内存中不同的位置,因此不相同。
str3和str4作为字符指针,他们都指向了常量区中的同一个字符串常量,因此二者相同。
2、指针数组指着数组,顾名思义是用来存放指针的数组
如: int* arr2[6];//存放整形指针的数组。
char* arr3[5];用来存放一级字符指针的数组
char**arr4[5];用来存放二级字符指针的数组
简单回顾后我们开始下面正篇的学习。
3、数组指针 3.1数组指针的定义数组指针是什么?答案便是指向数组的指针
下方的代码哪个是数组指针?
int *p1[5]; int (*p)[5]; 很明显,第二个是数组指针,它指向的是一个能存储5个整形数据的整形数组,而第一个则是存放整形指针的数组,即指针数组。
解释:
int (*p)[5]; //解释,p先和*结合,说明p是一个指针变量,然后指着指向的是一个大小为10个整型的数组。所以p是一个指针,指向一个数组,叫数组指针。3.2&数组名和数组名的区别
首先随便创建一个数组:int arr[10];
&arr 和arr有什么区别?
&arr取出的是整个数组的地址,而arr是数组首元素的地址。(sizeof(arr)中arr是整个数组的地址,该用法的注意事项就不多赘述了)。
由上图可知,二者打印出的地址相同(这边忘记改成x86了,不过不影响),这里似乎看不出区别,接下来便展示他们的区别。
相信看到这里便很明显了,同样是+1但两者地址相差了36,即9个整形数据的大小,由此可知,&a+1是向后移动了一个数组的大小,而a+1则是一个整形的大小。
3.3数组指针的使用通过上面,我们了解了数组指针,但应该如何进行使用呢?
#includeint main(void) { int arr[10] = {1,2,3,4,5,6,7,8,9,0}; int (*p)[10] = &arr;//把数组arr的地址赋值给数组指针变量p //但是我们一般很少这样写代码 return 0; } 下面来个简单的使用(此处的使用主要作为了解的作用,具有实用价值的应用之后会提) //下面我们通过数组指针的方法来打印二维数组 #include void print_arr(int(*p)[5],int row,int col) { for(int i=0;i for(int j=0;j
printf("%d",*(*(p+i)+j));//该写法与p[i][j]相同 } printf("n"); } } int main(void) { int arr[3][5]={0,1,2,3,4,5,6,7,8,9}; print_arr(arr,3,5); //数组名arr表示首元素的地址 //但二维数组的数组名是表示二维数组的第一行 //所以这里传递的arr,其实相当于第一行的地址,是一维数组的地址 //可以数组指针来接收 return 0; }
在经过上面的了解后让我们来看看以下代码:
int arr[5]; //整形数组 int *parr1[10]; //指针数组 int (*parr2)[10]; //数组指针 int (*parr3[10])[5]; //数组指针数组 最后一个是否能看懂?parr3先和[10]结合表面这是一个数组,而数组内存储的数据类型便是int(*)[5],即数组指针,因此这是一个数组指针数组。4、数组传参和指针传参
在我们写代码时,难免需要把数组或者指针传给函数,函数的参数该如何设计才符合规范呢?
4.1一维数组传参分辨以下函数参数的设计是否有问题:
#include4.2二维数组传参void test(int arr[]){} //无问题 void test(int arr[5]){} //无问题 void test(int *arr){} //y void test2(int *arr[10]){} //y void test2(int **arr){} //y int main(void) { int arr[5]={0}; int *arr2[10]={0}; test(arr); test(arr2); //arr2是数组首元素地址,因此其类型是int *型。 return 0; }
void test(int arr[3][5])//y
{}
void test(int arr[][])//n
{}
void test(int arr[][5])//y
{}
//总结:二维数组传参,函数形参的设计只能省略第一个[]的数字。
//因为对一个二维数组,可以不知道有多少行,但是必须知道一行多少元素。
void test(int *arr)//n
{}
void test(int* arr[5])//n
{}
void test(int (*arr)[5])//y
{}
void test(int **arr)//n
{}
int main()
{
int arr[3][5] = {0};
test(arr);
//arr代表二维数组的第一行的地址,因此需要使用数组指针来进行接受
}
4.3一级指针、二级指针传参
一级指针传参
void print(int *p,10){}
int main(void)
{
int arr[10]={0,1,2,3,4,5,6,7,8,9};
int *p = arr;
print(p,10);
return 0;
}
接下来让我们思考以下当一个函数的参数为一级指针的时候能够接收哪些数据?
int *p 、 int arr[10]的数组名 、一级指针变量、
二级指针传参
#include5、函数指针void test(int** ptr) { printf("num = %dn", **ptr); } int main() { int n = 10; int*p = &n; int **pp = &p; test(pp); test(&p); return 0; } 接下来让我们思考以下当一个函数的参数为二级指针的时候能够接收哪些数据? int**p , int* arr[]的数组名,二级指针变量。
数组有数组指针,那么我们平时经常使用的函数当然有函数指针啦。
函数名和&函数名都是函数的地址。
下方两个代码谁是函数指针
void (*test)();
void *test();
很明显是第一个,test先和*结合说明是指针,指向一个无参返回值为void的函数。
接下来我们来欣赏两段有意思的代码:
//代码1
(*(void (*)())0)();
//代码2
void (*signal(int , void(*)(int)))(int);
接下来博主进行一个个解释,相信大家会收获很多
代码一:(*(void (*)())0)()中(void (*)())0是对0这个值进行强制转换为函数指针的形式,即地址为0处有参数为空,返回值为void的一个函数。接着(*(void (*)())0)()中*号是对该处进行解引用,进行函数的调用。
代码二:void (*signal(int , void(*)(int))(int);
为了方便理解,我们来进行简化,即typedef void(*pf)(int)
由此我们可以简化为pf signal(int,pf);
由此便可轻松看出signal是一个参数为 int 和 void(*)(int),返回值为void(*)(int)的函数。
函数指针实现简易计算器
接下来让我们敲一段小程序,通过函数指针来实现计算器小程序。
#define _CRT_SECURE_NO_WARNINGS #includevoid menu(); int add(int a, int b); int sub(int a, int b); int mul(int a, int b); int div(int a, int b); void calc(int (*p)(int, int)) { int a = 0; int b = 0; printf("请输入两个操作数n"); scanf("%d %d", &a, &b); int num = p(a, b); //加法函数 printf("计算结果为%dn", num); } int main(void) { int input = 0; //用户输入值 do { menu(); //打印菜单 printf("请选择->:"); scanf("%d", &input); switch (input){ case 1: calc(add); break; case 2: calc(sub); break; case 3: calc(mul); break; case 4: calc(div); break; case 0: printf("退出计算器n"); break; default: printf("输入非法,请重新输入n"); break; } } while (input); return 0; } void menu() { printf("***********************n"); printf("*****1、add 2、sub*****n"); printf("*****3、mul 4、div*****n"); printf("*****0、exit *****n"); printf("***********************n"); } 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; //该程序主要起到练习作用,因此不考虑细节。 }
大家可以看到,函数指针的使用大大简化了我们的代码,而接下来的函数指针数组的简化力度更大。另外大家可以看到我们通过给calc函数传参其他函数的地址来实现函数调用,这也就是我们平时所说的的回调函数,在第八部分我们会着重其来讲解。
6、函数指针数组 函数指针数组实现简易计算器includevoid menu() //打印菜单 { printf("***********************n"); printf("*****1、add 2、sub*****n"); printf("*****3、mul 4、div*****n"); printf("*****0、exit *****n"); printf("***********************n"); } 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(void) { int input = 0; //用户输入值 int (*p[5])(int, int) = { 0,add,sub,mul,div }; //函数指针数组 int a = 0; int b = 0; do { menu(); //打印菜单 printf("请选择->:"); scanf("%d", &input); if (input >= 1 && input <= 5) { printf("请输入两个操作数:"); scanf("%d %d", &a, &b); printf("%dn", p[input](a, b)); } else if (input == 0) { printf("退出计算器n"); } else { printf("输入非法,请重新输入n"); } } while (input); return 0; }
通过上方的代码,大家可以很清楚的看到,通过运用函数指针数组,我们的代码得以大大简化了。
7、指向函数指针数组的指针指向函数指针数组的指针是一个指针,指针指向了一个数组,数组的元素都是函数指针。
#includeint (*arr[5])(int ,int ); int (*(*parr)[5])(int ,int )=&arr; //指向函数指针数组的指针 return 0; }
此处可无限套娃下去,这里就不多赘述了。
8、回调函数回调函数就是一个通过函数指针调用的函数。如果你把函数的指针(地址)作为参数传递给另一个函数,当这个指针被用来调用其所指向的函数时,我们就说这是回调函数。回调函数不是由该函数的实现方直接调用,而是在特定的事件或条件发生时由另外的一方调用的,用于对该事件或条件进行响应。
回调函数的使用我会通过库函数qsort的模拟实现来讲解,因为内容过多这里我会在之后附上专门讲库函数qsort的模拟实现的博客链接。(已经发布)
https://blog.csdn.net/m0_67530233/article/details/125461583?spm=1001.2014.3001.5502
指针的用法真的是很多,希望大家都能通过我的这篇博客收获新的知识,求个赞( ̄▽ ̄)"。



