字符指针变量
在指针的类型中我们知道有一种指针叫做字符指针
它的使用情况如下:
#include<stdio.h>
int main()
{char pa = 'w';char*p1=&pa;*p1 = 'a';printf("%c\n", *p1);return 0;
}
在这段代码当中,我们将‘w’字符的地址传到了p1里面,而p1就是一个字符指针。
除了上面这种使用方法,还有一种关于字符指针变量的使用方法如下:
#include<stdio.h>
int main()
{char* p1 = "abced";printf("%s\n", p1);return 0;
}
有没有感到很好奇,对于这段代码的解释,大家有什么想法吗?
咱们的第一个反应应该是,这个是不是把字符串“abced”放到字符指针p1里面啊,但真实情况不是这样的,这个是将“abced”中的首字母的地址放到了p1的指针变量当中,从而在打印的时候,可以通过p1找到字符串首字母的地址,从而顺藤摸瓜地打印出整个字符串。
下面呢,我们来看一段代码,这段代码是一道经典的面试题,来自《剑指offer》
#include<stdio.h>
int main()
{char arr3[] = "hello,bit.";char arr4[] = "hello,bit.";char* p1 = "hello,bit.";char* p2 = "hello,bit.";if (arr3 == arr4){printf("arr3 and arr4 are the same\n");}elseprintf("arr3 and arr4 are not same\n");if (p1 == p2){printf("p1 and p2 are the same\n");}elseprintf("p1 and p2 are not same\n");return 0;
}
下面是这段代码运行的结果
这个结果大家想到了吗??
其中的原理如下:
在数组中,用相同的常量字符串初始化数组时,系统会开辟不同的内存空间。
而在指针当中,两个指针指向的是同一个常量字符串,也就是指向同一个开辟下的内存空间。这个就是上面答案的原理所在。
数组指针变量
前面我们讲了指针数组,指针数组是一个数组,存放的是指针(地址),而数组指针是一个指针,存放的是数组的地址,看下面两个:
//指针数组和数组指针
//1.int*arr[10]
//
//2.int(*arr)[10]
大家可以看看这两个,哪个是指针数组,哪个是数组指针。
很明显,第一个是指针数组,数组名是arr,数组中存放有10个元素,每个元素是int*类型
然后第二个是一个数组指针,根据优先级考虑,在这个当中,首先arr应该与*结合,构成一个指针,然后指向的是一个10个元素的数组,数组中的每个元素都是int类型
数组指针变量的初始化
数组指针是一个指针,存放的应该是数组的地址,那我们怎么可以得到数组的地址呢,&arr,通过这个便可以得到数组的地址
#include<stdio.h>
int main()
{int arr[10] = { 0 };int(*pte)[10] = &arr;return 0;
}
从这个当中我们可以看到,&arr和pte的地址是相同的,二者指向了同一块内存空间,这个就是数组指针变量的初始化。
二维数组传参的本质
有了前面的数组指针变量的基础,我们就可以好好地了解一下二维数组传参的内容,之前,我们写过二维数组传参的内容,请看下面的代码:
#include<stdio.h>
void print(int arr[3][4], int r, int j)
{for (int i = 0; i < r; i++){for (int h = 0; h < j; h++){printf("%d ", arr[i][h]);}printf("\n");}}
int main()
{int arr[3][4] = { {1,2,3,4},{2,3,4,5},{3,4,5,6} };print(arr, 3, 4);return 0;
}
我们在之前的代码当中,形参是数组形式,实参也是数组的形式,除了这个写法,我们还有其他的写法吗?
我们再来看二维数组,二维数组其实可以看成是每个元素都是一维数组的数组,然后数组名是数组首元素的地址,也就是说,实参的第一个参数的意思是二维数组第一行的四个元素的地址,所以,我们可以在形参部分写成这样:
#include<stdio.h>
void print(int (*ptr)[4], int r, int j)
{for (int i = 0; i < r; i++){for (int h = 0; h < j; h++){printf("%d ", *(*(ptr+i)+h));}printf("\n");}}
int main()
{int arr[3][4] = { {1,2,3,4},{2,3,4,5},{3,4,5,6} };print(arr, 3, 4);return 0;
}
在这里,我们在详细讲一个*(*(ptr+i)+h),首先,先看最里面的那个括号,ptr是数组首元素的地址,ptr+i代表着二维数组的第几行的地址,然后再加*找到第几行的元素,也就是arr[i],然后用arr[i]+j代表的是第i行第j列的地址,然后在使用解引用符,就可以找到该地址所代表的元素,然后打印出来,就是数组。
数组名,是数组首元素的地址
&数组名,是数组的地址
函数名,是函数的地址
&函数名,也是函数的地址
二维数组传参,形参可以形成数组形式,也可以写成指针形式。
函数指针变量
通过前面对于指针变量的理解,我们大概可以知道,函数指针变量应该是一个指针变量,指向的应该是函数的地址,那么问题来了,函数有地址吗?让我们来看一下:
#include<stdio.h>
int main()
{printf("printf = %p\n", &printf);return 0;
}
从上面的结果,我们可以看到,函数是有地址的,函数名就是函数的地址,那我们也可以通过&函数名来获得函数的地址,那我们也可以通过函数地址的调用实现对于函数的调用。
如果我们将函数变量的地址存放起来,就可以创建函数指针了,函数指针其实和数组指针是极其相似的。
void test()
{printf("hehe");
}
void (*ptr1)() = test;
void(*ptr2)() = &test;int Add(int x, int y)
{return x + y;
}
int (*ptr3)(int, int) = Add;
int (*ptr4)(int x, int y) = &Add;//加不加 x,y都是可以的
函数指针类型分析:
函数指针变量的使用
通过函数指针调用指针所指向的函数
#include<stdio.h>
int Add(int x,int y)
{return x + y;
}
int main()
{int (*ptr)(int, int) = Add;printf("%d\n", (*ptr)(3, 4));printf("%d\n", ptr(3, 4));return 0;
}
两段有趣的代码
接下来,我们来看两段出自《C陷阱和缺陷》这本书中的代码:
(*(void(*)())0)()
//第一段:
//(*(void(*)())0)()
/*在这段代码当中:
* 第一步:void(*)()这个是函数指针的类型
* 第二步:(void(*)())0----这个是对0的强制类型转换,使0转换成函数指针类型
* 第三步:(*(void(*)())0)()----是一个指针
*/
第二段:
第二段:
void (*signal(int, void(*)(int)))(int);
第一步:signal是一个函数名
第二步:函数名后面的(int, void(*)(int))是函数参数
第三步:整个又是一个函数指针,参数类型是int
typedef关键字
typedef是关键字没在C语言当中可以起到重命名的作用
typedef int* unit;
typedef void(*ptr)(int);
typedef void(*deff)();
函数指针数组
我们之前学了指针数组,同理,函数指针数组是将函数指针放入数组当中,那么这个该怎么实现呢??
//函数指针数组
void (*)()ptr[10] =;