C语言-指针Ⅱ

本篇是对c语言的指针内容进行一个进阶的学习。

1.字符指针

字符指针,顾名思义,就是指针指向的数据类型是字符型的指针,如下图

char n='w';
char* p=&n;

这里的p就是一个字符指针,但是字符指针有一个特殊的用法,我们看下图

char* p="abcdef";
printf("%s\n",p);

在这个代码中,我们直接把一个字符串"abcdef"赋给了p,那么这时候我们到底是把"abcdef"的值赋给了p呢还是把"abcdef"的地址赋给了p呢?
运行上面的代码可以发现,编译器直接打印出了abcdef,所以,当我们直接把abcdef这个字符串直接赋给指针p时,其实是把这个字符串中第一个元素a的地址赋给了它,在打印时,因为我们是%s按字符串打印,所以编译器就会从p指向的地址开始打印一个字符串,因为"abcdef"后面是有"\0"的,所以就可以原模原样的把这个字符串打印出来。
还有一点值得注意的是,在上面的代码中,我们是直接把字符串"abcdef"赋给了p,而这个字符串就是一个常量字符串,常量的意思就是它不能被改变,那么我们想既然它不能被改变,如果我多次使用的时候那就没必要再多次创建它了,所以当我们创建出一个常量时,内存中只会开辟出一块空间来存放它,当我们想多次使用它时,并不会开辟新的空间来创建这个常量。

2.指针数组

与我们在《C语言-指针Ⅰ》中说到的一样,指针数组就是指元素是指针的数组,本质还是数组
我们来看下面的代码

int main()
{
    int data1[]={1,2,3,4,5};
    int data2[]={2,3,4,5,6};
    int data3[]={3,4,5,6,7};
    int* arr[3]={data1,data2,data3};
    int i;
    for(i=0;i<3;i++)
    {
        int j;
        for(j=0;j<5;j++)
            printf("%d ",arr[i][j]);//*(arr[i]+j)
        printf("\n");
    }
    return 0;
}

首先我们创建了3个数组,然后我们又创建了一个指针数组,然后我们把这三个数组当作这个指针数组的三个元素,我们知道数组名代表这个数组第一个元素的地址,那么这三个数组名就刚好是三个地址,符合指针数组元素的条件,然后在我们打印时,以data1为例,这里的arr[1]<==>data1,所以我们就可以用arr[ i ][ j ]来访问数组的第j个元素,而我们知道这些元素也是地址,所以我们也可以用指针的方法(arr[i]+j)来找到第i个数组的第j个元素的地址,然后*(arr[ i ]+j)访问它。

3.数组指针

主语是指针,所以说明它是一个指针
我们有整型指针:

int a=10;
int* p=&a;

即指向整型的指针
字符指针:

char b='w';
char* p=&b;

即指向字符类型的指针
那么顾名思义,数组指针就是指向数组的指针
那么我们要怎么定义一个数组指针呢

int arr[5]={1,2,3,4,5};
int* p=arr;//这里的p是指向数组第一个元素的整型指针,不是数组指针

在上面的代码中我们&a得到整型a的地址,&b得到字符型b的地址,那么我们&arr就可以得到数组arr的地址

int arr[5]={1,2,3,4,5};
int(*p)[5]=&arr;

这时候的p就是一个数组指针了。
我们可以看下面的代码来区分数组指针与指针数组

int* arr[10];      //这个数组的元素类型为int*,而int*指指向内容为int类型的指针,所以这是一个指针数组
int*(*pa)[10]=&arr;//(*pa)告诉我们pa是一个指针,然后这个指针指向的是一个元素类型是int*,长度为10的数组,所以这时数组指针

​注意[ ]优先级是高于*的,所以我们要在数组指针上加上()才能正常书写。

我们知道数组名一般就是指数组的第一个元素的地址,那么在上面出现的 &数组名 是指数组的地址,那么他们之间有什么联系呢?

​​在这里插入图片描述
由这个运行结果我们知道,其实&arr,arr,&arr[ 0 ]在数值上都是arr这个数组的第一个元素,那么他们到底有什么区别呢?
在这里插入图片描述
我们把这三个地址加1打印,发现&arr与其他两个有点不一样,p1 p2的地址加一是原地址加4个字节,而arr是一个整型数组,一个元素的大小也是四个字节,说明p1 p2加1是以一个元素为单位跳的,而p3加1是在p3的位置加了20个字节,而arr的长度是5,一个元素的大小是4个字节,说明p3加1是以一个arr数组为单位跳转的,所以说&arr取出的是数组arr的地址。
​ 以上是数组的地址与数组的首元素的地址的区别,那么我们现在回到数组指针上来,数组指针是怎么使用的呢?
数组指针的使用一般常见于二维数组,比如说我现在有一个二维数组,我想要写一个函数实现对这个二维数组的打印,我们就可以用到数组指针,看下图
在这里插入图片描述
这是我们不使用数组指针的方法,那么如果我们想用数组指针,应该如何实现这个代码呢?
在这里插入图片描述
我们发现,如果我们要使用数组指针来访问二维数组中的元素,当我们把这个数组的地址传过去的时候,函数接收的其实是这个二维数组第一行的数组的地址,p+i相当于拿到了第i行的地址,(p+i)相当于是第i行这个数组的数组名,也就是它的首元素地址,那么当我们要访问这一行数组里的元素时,就可以用((p+i)+j)的方式来访问,同时,我们知道(p+i)也表示第i行的数组的名字,那我们是不是也可以用((p+i))[ j ]的方式来访问第i行的元素呢?
在这里插入图片描述
我们发现这样也是可行的,说明
((p+i)+j)<==>((p+i))[ j ] (一定要注意*(p+i)外面的括号,因为[]的优先级是比*高的)
我们再来区分一下几个代码

int arr[5];      //数组,5个元素,元素类型为int
int* p1[10];     //数组,10个元素,元素类型为int*--指向int类型的指针
int(*p2)[10];    //指针,元素类型为int( )[10]--数组,十个元素,每个元素都是int类型
int(*p3[10])[5]; //数组,10个元素,每个元素存放的都是一个数组,数组是5个元素,int类型的

上面的这三个应该是比较好理解的,我们来仔细分析一下第四个,首先,我们可以先把括号里的内容拿出来,p3[10],这p3与是没有()的,因为[]的优先级比()高,所以可以判断这是一个数组,而这个数组里存放的元素的类型为int(*)[5]–是一个指针,指向的是一个元素类型为int,元素个数为5的数组。那么它代表的到底是什么呢?,其实就是比如我们有10个数组,每个数组都是int 【5】,如何我们要把这10个数组的地址都存放到一个指针中,构成一个指针数组,而这个指针数组就是p3。

4.数组参数与指针参数

在一维数组中,我们知道,数组名等于数组第一个元素的地址,那么当我们把数组名作为函数的参数时,函数可以用什么类型来接收呢?

void test1(int arr[10])  //参数类型与传入的类型一样,完全可行
void test1(int arr[])    //与第一个的差别是没有指定数组长度,但是在函数中我们并不创建新的数组,只是接收数组第一个元素的地址,所以这里的长度并不重要,可以不写。
void test1(int *arr)     //因为传入的是数组第一个元素的地址,数组的元素是int,所以用int*的指针接收也可行
void test2(int *arr[20]) //与参数类型完全一样,可行
void test2(int **arr)    //因为传入是一个指针数组,指针数组的元素都是指针,那么指针数组第一个元素的地址就可以用二级指针来接收,可行。
int main()
{
    int arr1[10]={0};
    int *arr2[20]={0};
    test1(arr1);
    test2(arr2);
    return 0;
}

那么在二维数组中,函数应该怎样接收参数呢?

void test(int arr[3][5]) //与参数类型一样,可行
void test(int arr[][5])  //与二维数组的定义类似,可以省略行,但是不能省略列
void test(int *arr)      //这是一个整型指针,而我们二维数组传来的是第一行的数组的地址,所以不可行
void test(int *arr[5])   //这是一个数组,只不过里面的内容是int*的指针,所以也不可行
void test(int (*arr)[5]) //这是一个数组指针,并且指针指向的数组的类型与传入的第一行的数组类型相同,可行
void test(int **arr)     //与二级指针无关,不可行
int main()
{
    int arr[3][5]={0};
    test(arr);
    return 0;
}

5.函数指针

我们用老方法来分析这个词语的主语,是指针,说明这是指一个指向函数的指针
那么整型指针我们是把整型的地址赋给指针,数组指针我们是吧数组的地址赋给指针,函数指针是不是把函数的地址赋给指针呢?
那么什么是函数的地址呢,我们可以写一个函数,然后打印这个函数的地址,来观察一下
在这里插入图片描述
我们发现,函数真的有地址,而且 函数名 与 &函数名 都是指这个函数的地址。
那么我们把这个地址赋给一个指针,这个指针的类型应该怎么写呢?
在这里插入图片描述
以我的加法函数为例,指针为int(p)(int,int) 首先要在指针变量前面加上,然后用括号括起来,在后面的括号里要写上这个函数的参数的类型,在前面再加上这个函数的返回值类型就可以了。
我们可不可以通过函数指针来使用这个函数呢,答案是可以的
在这里插入图片描述
我们发现,我们用指针来使用函数的方法是可行的,而且我们还发现了一点,在我们通过指针来使用函数时,指针变量前面加不加其实并没有影响,也就是说,在指针变量前面的这个其实没有用途,它的存在只是为了让人更容易理解。

6.函数指针数组

同样,分析主语,是一个数组,数组的元素都是函数指针。
那么函数指针数组怎么定义呢?我们看下面的代码

int ADD(int x,int y)
{
    return x+y;
}
int SUB(int x,int y)
{
    return x-y;
}
int main()
{
    int(*pf1)(int,int)=ADD;         //pf1就是一个函数指针,指向函数ADD
    int(*pf2)(int,int)=SUB;         //pf2也是一个函数指针,指向函数SUB
    int(*pf[5])(int,int)={ADD,SUB}; //pf就是一个数组,元素是函数指针,一个ADD,一个SUB,数组的不完全初始化
}
    

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

我们以数组指针为例

int* arr[10];
int(*pa)[5]=&arr;

arr是一个指针数组。对arr取地址赋给pa,pa就是一个指向指针数组的指针,那么我们把arr这个指针数组换成函数指针数组,再对arr取地址不就是指向函数指针数组的指针了吗

int(*pa)(int,int);//pa是一个函数指针
int(*paarr[5])(int,int)={pa};//paarr是一个函数指针数组
int(*(*ppa)[5])(int,int)=&paarr;//ppa就是一个指向函数指针数组的指针

8.回调函数

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

我会在下一篇写简易计算器时写到如何用回调函数的方法完成一个简易计算器。
如果大家觉得我的文章有用,还请大家多多支持,点赞,关注!!

  • 2
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

c铁柱同学

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值