进阶的内容比较庞大,但请各位耐心看完,我相信一定会有所收获,如果我有错误的地方希望大家可以指正我的错误,万分感谢

防止有些博友没有看过前一篇我的博客,在此简单概括一下那篇博客的内容有哪些。

  1. 指针变量是用来存放地址的,对其解引用就可以找到该地址唯一标识的内存空间
  2. 指针大小在32位平台上为4字节,在64位平台上为8字节
  3. 指针具有类型之分,指针类型决定了其进行解引用操作时的访问权限及+/-整数的步幅
  4. 指针的几种运算(指针加减整数,指针减指针,指针关系运算)

 

本文章主要内容

 

1.字符指针

我们知道字符指针的使用方法为

#include<stdio.h>int main(){char a='0';char *p=&a;*p='a';return 0;}

 

那下面这种是什么意思呢

#include<stdio.h>int main(){char *p="Thanks for browsing";return 0;}

 

我们知道char*只能存一个元素的地址,但怎么可以和一个字符串相等呢?
C语言指针全归纳-进阶版_C语言

有次我们看到,原来上面这个代码的意思是存放该字符串首元素的地址(即‘T’的地址存放在p中)
那么我们就可以这样使用它了
C语言指针全归纳-进阶版_C语言指针_02
但是这样写有个小问题,这里的"Thanks for browsing"和存放于数组中的字符串不同,是不可更改的,被称为常量字符串,它在内存里只会存储一份。
不理解只存一份的可以点我哦

2.指针数组

初阶已介绍指针数组,即存放指针的数组就是指针数组
在此就不再赘述,主要是为了区分下面的数组指针

int *arr1[10];//存放一级整形指针的数组int **arr2[10];//存放二级整形指针的数组

 

3.数组指针

数组指针重点说的是指针
例如整形指针是指向整形的指针、字符指针是指向字符的指针
那么数组指针就是指向数组的指针

int arr[10]={0};int (*p1)[10]=&arr;int *p2=arr;//*p代表这是个指针,因为[]比*的结合性要高,会与指针数组产生歧义,所以需要加上()//即int (*p1)[10]=&arr的完整解释为p是一个指向有十个整形元素的指针

 

&arr是指整个数组的首地址
arr是指首元素的地址
两者虽数值上相同,但加1后移动的步幅不同!(如下图)

C语言指针全归纳-进阶版_C语言_03
数组名只有在两种情况下是指整个数组的首地址

  1. &arr
  2. sizeof(arr)

除此之外数组名均指代数组首元素地址

接下来我们来介绍一下数组指针的使用方法

#include<stdio.h>int main(){int arr[10] = { 0 };int i = 0;int(*p)[10] = &arr;for (i = 0; i < 10; i++){printf("%d ", (*p)[i]);printf("%d ", *((*p)+i));printf("%d ", (*p)[0][i]);//以上三种均可找到数组中的元素//*p等价于arr,即arr[i]==(*p)[i]//&具有‘升维’的作用,*具有‘降维’的作用}return 0;}

 

C语言指针全归纳-进阶版_C语言指针_04

#include<stdio.h>void print(int (*p)[5],int r,int c){int i = 0;for (i = 0; i < r; i++){int j = 0;for (j = 0; j < c;j++){printf("%d   ", *(*(p+i)+j));//p+i相当于&arr+i  第i行的地址//*(p+i)+j相当于arr+j  第j个的地址}printf("\n");}}int main(){int arr[3][5] = { { 1, 2, 3, 4, 5 },{ 6, 7, 8, 9, 10 },{ 11, 12, 13, 14, 15 } };print(arr, 3, 5);//二维数组传参,传的是第一行的地址//传参数也会发生降维//传二维数组,函数得到的为第一行的地址//传一维数组,函数得到的为首元素地址return 0;}

 

这里做一个小练习,可以测试一下自己是否真的明白了指针数组和数组指针

int arr[5];                 有五个整形元素的数组int *p[5];                  有五个整形指针的数组int (*p)[5];                指向有五个整形元素的数组的指针int (*p[5])[10];            存放五个指向有十个整形元素的数组的指针的数组 
//p先和[]结合成为数组,然后拆解为int (*)[10],//现在即使数组中的元素类型,每个元素为指向有十个整形元素的数组指针

 

4.数组传参和指针传参
一维数组传参void test1(int arr[]){}//该方法就是普通的数组传参void test2(int arr[10]){}//该方法与test1相同,10并没有实际意义,只是形式上感觉上下匹配void test3(int *p){}//该方法就是利用了数组传参会发生降维,就利用整形指针接收void test4(int *p[10]){}//同test2void test5(int **p){}//传的元素为一级指针,所以可以用二级指针接收int main(){int arr[10] = { 0 };int *p[10] = { 0 };test1(arr);test2(arr);test3(arr); 
	test4(p); 
	test5(p);return 0;}

 

二维数组传参void test1(int arr[3][5]){}//该方式可行void test2(int arr[][5]){}//该方法可行void test3(int arr[][]){}//该方法不可行,对二维数组而言,可以不知道行数,但必须知道列数void test4(int *arr){}//该方法不可行经降维后得到是一维数组的地址,而此处却用整形指针接收void test5(int* arr[]){}//该方法不可行,这里用的是指针数组接收,要想用数组接收只能用上面两种void test6(int (*p)[5]){}//该方法可行,利用数组指针指向一个一维数组void test7(int **p){}//该方法不可行,该方法是为了接收一级指针。利用指针接收只能用上面这种int main(){int arr[3][5] = { 0 };test1(arr);test2(arr);test3(arr);test4(arr);test5(arr);test6(arr);test7(arr);return 0;}

 

我们知道一级指针传值,可以用一级指针接收
那么,反过来想想,都有哪些传过去可以用指针接收呢?void test(int *p){}int main(){int a = 10;int *pa = &a;int arr[5] = { 0 };test(&a);test(pa);test(arr);//传参基本原则:传过去的参数和形参类型相同return 0;}同理,二级指针呢?void test(int **p){}int main(){int a = 10;int *pa = &a;int **ppa = &pa;int *arr[10] = { 0 };test(ppa);test(&pa);test(arr);return 0;}

 

5.函数指针
#include<stdio.h>int test(int x,int y){return x*y;}int main(){int a=0;int *pa=&a;//指向整形的指针int arr[10]={0};int (*parr)[10]=&arr;//指向数组的指针int    (*pf)(int ,int)=&test;//指向函数的指针//函数返回值类型   参数类型   函数名取地址即可获得函数地址test==&test 两者相同,上面可替换,这里可没有函数首元素一说
     函数指针如何调用函数呢?int b=(*pf)(2,3);//和正常使用指针是类似的,只需解引用即可使用return 0;}

 

但上面已经写过,将test赋给pf,那么是否意味着pf也可以和test一样直接调用函数呢?
答案是肯定的,可以。pf前面加星号是为了初学者可以更快了解C语言语法,其实 并未起到解引用的用处,但如果写成(pf)(2,3)的形式就一定要带括号,否则意思就变成了对调用函数的结果解引用
C语言指针全归纳-进阶版_C语言_05
现在我们已经初步认识函数指针,接下来我们来看看两句很“有趣”的代码

1.(*(void(*)())0)()

 

  1. 这个的确很不好看明白,但首先我们知道void(*)()是一个不需要参数且返回值为空的函数指针类型,而 ()0就是将0强制类型转换为函数指针类型,接着就是解引用调用0地址处的函数,该函数无参数,返回类型为void。
    (该代码源自C陷阱和缺陷)
    C语言指针全归纳-进阶版_C语言_06
2.void(* signal(int, void(*)(int) ) )(int);

 

  1. 首先要肯定的是这是一个函数声明,signal是函数名,需要整形和函数指针(该函数指针参数为int,返回值类型为void)两个参数,signal的返回值类型为void(* )(int)(该函数指针参数为int,返回值类型为void)。
上面这个其实不容易理解,所以我们可以利用typedef让它变得好看一点typedef void(*new_pf)(int) ;注意new_pf为新名字,void(*)(int)为旧名字
这样写是语法规定,不可以写成typedef void(*)(int) new_pf;所以上面的函数声明就可以写成  new_pf signal(int,new_pf);

 

6.函数指针数组

这个应该可以举一反三了吧,函数指针数组即存放函数指针的数组

#include<stdio.h>void test1(){printf("hello\n");}void test2(){printf("world\n");}int main(){void(*pt1)() = test1;void(*pt2)() = test2;void(*parr[2])() = (test1, test2);//函数指针数组其实就是在函数指针的名字后面加 [元素个数]return 0;}

 

运用函数指针数组很经典的案例函数指针数组实现计算器(可以点进来了解一下)

7.指向函数指针数组的指针

函数指针数组终归是个数组,既然是数组,那就可以用指针指向它

int (*p)(int ,int);//函数指针int (*parr[5])(int ,int);//函数指针数组int (*(*pparr)[5])(int ,int)= &parr;//指向函数指针数组的指针(*pparr)证明是个指针,[5]说明指向的是个数组,元素类型为int (*)(int ,int)

 

8.回调函数

注意重点来了

回调函数就是一个通过函数指针调用的函数,如果你把函数的指针(地址)作为参数传递给另一个函数,当这个指针被用来调用其所指向的函数时,我们就说这是回调函数。回调函数不是由该函数的实现方直接调用,而是在特定的事件或条件发生时由另外的一方调用的,用于对该事件或条件进行相应。

举个例子void test1(){ printf("hello");}void test2(void (*p)()){
	 p();}int main(){test2(test1);并没有主动调用test1,而是将test1地址传给test2,在适当情况下test2调用test1
    此时test1被称为回调函数return 0;}

 

如果想加深一下对回调函数的理解可以点下面两个链接
回调函数计算器
快速排序的使用(qsort就是一个很好的回调函数)

大家好,上次的初阶版让我第一次感受到了写博客喜悦,原来可以有这么多人看我的博客呀,非常感谢大家对我的支持与鼓励,我一定会再接再厉,继续创作出优质的内容来向大家分享知识。——2021.3.23

今天终于把它磨出来了,虽然有些小细节还是可能还需更改,但内容上还是很充实的,非常感谢点进来的每个人。——2021.4.4