1.字符指针变量:一般使用字符指针如下形式:
其实还有如下形式:
p里面放不下这么多个字符的,所以此时应该把这个hello world看成是一个字符数组,而p里面放的是这个字符串的首字符地址。打印验证一下:
与char*p=arr[ ]的区别:数组是可以变的,而上面这个是个常量字符串,变不了。稍微试一下:
调试之后报错了,既然不可以被修改,那么可以在旁边加一个const,变成const char*p。
那么就可以用以下方式打印:
int main()
{
char* p = "hello world";
printf("%s\n", p);
int len = strlen(p);
for (int i = 0; i < len; i++)
{
printf("%c", *(p+i));
}
printf("\n");
return 0;
}
这时候就有问题了,为什么%s的时候后面的p不用解引用操作。这个p就是告诉printf这个地址,就像是数组,给了数组名也可以打印,
#include <string.h>
int main()
{
char* p = "hello world";
printf("%s\n", p);
int len = strlen(p);
for (int i = 0; i < len; i++)
{
printf("%c", *(p+i));
}
printf("\n");
char arr[] = "abcdef";
printf("%s\n", arr);//数组名就是首元素地址
char* p2 = arr;
printf("%s\n", p2);
return 0;
}
有一道题:
int main()
{
char arr1 [] = "abcdef";
char arr2[] = "abcdef";
const char* arr3 = "abcdef";
const char* arr4 = "abcddef";
if (arr1 == arr2)
printf("1 and 2 are same\n");
else printf("1 and 2 are not same\n");
if (arr3 == arr4)
printf("3 and 4 are same\n");
else printf("3 and 4 are not same\n");
return 0;
}
结果是:
创建两个数组,虽然内容一模一样,但是它们是两块独立的空间,arr1和arr2是数组名,表示首元素地址,地址不相同,所以不一样。常量字符串在不能修改,所以在内存中也没有必要存两份了,都是一样的,共用一份,那么把这个字符串放到两个不同的指针变量,但是指向的地址都是这个字符串,所以地址一样,就是一样的。
2.数组指针变量:类比整型指针和字符指针,都是指针,一个指向整型,一个指向字符,所以数组指针也是指向数组的,里面放的是数组的地址。上一节有数组名的理解,&arr才是数组的地址,这一整个数组是一个单位,那么问题来了,怎么写数组指针?
首先我需要它是一个指针,就得和*联系起来,那么就可以:
这样就说明它是个指针,[10] 说明它指向的是数组,数组十个元素,int说明元素类型是int。把指针变量的名字去掉就是类型,那么这个指针变量的类型就是int(*)[10]。关于前面的int arr[10], int(* p)[10]=&arr+1跳过了40个字节也可以用这个解释。这个数组指针指向的是一个数组,指针类型决定了它+1跳过几个字节,所以+1要跳过一个数组。
例:
int main()
{
int arr[10] = { 1,2,3,4,5,6,7,8,9,10 };
int(* p)[10] = &arr;//指向整个数组,+1会跳过整个数组
//所以需要对p进行解引用操作,就可以拿到这个数组,然后通过下标访问元素
int i = 0;
int sz = sizeof(arr) / sizeof(arr[0]);
for (i = 0; i < sz; i++)
{
printf("%d ", (*p)[i]);
//p=&arr,*p=*&arr=arr
}
return 0;
}
虽然这样可以操作,但是反而麻烦了,没有必要了。所以数组指针一般不用在一维数组。
3.二维数组传参本质:数组名是数组首元素的地址,那么二维数组的首元素地址就是第一行,就是把二维数组的一行看成一个元素。那么就可以把这个数组的数组名放到数组指针里面去,这样子这个指针就可以指向它的第一行。p+1就跳过一行,那么就有如下代码:
void test(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]);
//等价于printf("%d", *(*(p+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} };
test(arr, 3, 5);
return 0;
}
能这样写是因为数组在传参的时候本质是指针。
4.函数指针变量:顾名思义,就是存放函数地址的指针变量。验证一下函数是否有地址:
int Add(int a, int b)
{
return a + b;
}
int main()
{
int a = 10, b = 20;
int c = Add(a, b);
printf("%d\n", c);
printf("%p\n", &Add);
return 0;
}
但是如果跟数组一样去求函数名的地址的话:
int Add(int a, int b)
{
return a + b;
}
int main()
{
int a = 10, b = 20;
int c = Add(a, b);
printf("%d\n", c);
printf("%p\n", &Add);
printf("%p\n", Add);
return 0;
}
可以发现函数名和函数的地址是一样的。类比数组,数组名是数组首元素的地址,&arr是数组的地址,虽然值是一样的,但是意义却不一样。但是在函数里面,&函数名和函数名都表示函数的地址,没有区别。
(1)函数指针变量的创建:首先它是一个指针,那么就要创建这个指针变量之后还要把它括起来说明它是一个指针,而且它指向的是函数,就要一个括号,里面写的是参数类型,最后前面写上指向函数的返回类型。
如果去掉括号,就变成函数了。
(2)函数指针的使用:
int Add(int a, int b)
{
return a + b;
}
int main()
{
int (*pf1)(int, int) = &Add;
int (*pf2)(int, int) = Add;
int r1 = (*pf1)(10, 20);
int r2 = (*pf1)(10, 20);
printf("%d\n", r1);
printf("%d\n", r2);
return 0;
}
在正常调用函数的时候,输入函数名然后传参就可以了,函数名是地址,也就是地址后面给参数,我们把函数名给函数指针的时候,这个指针变量也就是地址了,所以,在使用的时候可以直接写上变量名,可以省略掉*。
int Add(int a, int b)
{
return a + b;
}
int main()
{
int (*pf1)(int, int) = &Add;
int (*pf2)(int, int) = Add;
int r1 = pf1(10, 20);
int r2 = pf1(10, 20);
int r3 = Add(10, 20);
printf("%d\n", r1);
printf("%d\n", r2);
printf("%d\n", r3);
return 0;
}
(3)利用所学知识解析下述代码:(*(void(*)())0)();
首先先看一下最显眼的部分:void(*)(),这个东西长得好像有点像上面的int (*p)(int, int),把p去掉的话就是int (*)(int, int),这东西好像是一个函数指针的类型,类比一下,void(*)()也是一个函数指针类型,指向的函数没有参数,返回类型是void类型。再往大的一点看,它被一个括号括了起来,那么括号括着一个类型好像是强制类型转换吧。然后这个强制类型转换的旁边有个0,0的话编译器默认为整型,但是旁边函数指针类型强制类型转换了,它变成一个地址了,变成了这个被指向函数的地址,然后它整个被括了起来,而且还有个*,这是解引用操作,地址是函数的地址,所以就是调用了这个函数,后面的括号是这个函数不需要传参数。所以它的第一步就是强制类型转换,第二步就是调用0地址处的地址。
再来一个:void (*signal(int, void(*)(int)))(int);有了上面的例子,这次应该可以一眼看出里面有两个类型:一个int类型,一个函数指针类型,这两个类型都被一个括号括了起来,旁边是*signal,类比一下,对于一个函数来说,名字后面只写了参数类型,这个好像就只是函数的声明吧。OK有了函数名和参数类型,还欠一个返回类型,那么就把函数名和参数全部去掉,就只剩下void (*)(int);那么我们可以一眼看出来这个东西是一个函数指针类型吧,所以这个函数的返回类型是一个函数指针类型。
所以总结一下就是这是一个函数的声明,函数名字叫做signal,它有两个参数,一个是int,一个是函数指针类型,然后这个函数指针可以指向一个函数,函数的参数类型是int,返回类型是void。然后这一整个signal函数的返回类型是void (*)(int);这样的一个函数指针。
理清这层关系之后我们可以把这个函数改写一下:void (*)(int)signal(int, void(*)(int)),这样是不是清晰了很多,但遗憾的是编译器不支持这种写法。接下来介绍一种方式可以简化这个代码。
(4)typedef关键字:可以将类型重命名,可以将复杂的类型简单化。比如说每次创建一个无符号的整型unsigned int num时,都要写这么长一个东西,那么就可以把unsigned int 前面写个typedef然后后面写个新名字:typedef unsigned int uint;下次就可以用这个新名字来定义无符号的整型uint num;以此类推,也可以对指针变量类型的进行重命名:typedef int* pint。但是要想对数组指针进行重命名的话,这种方式却行不通,正确的做法是把新的变量名写到*旁边。
下面对函数指针类型重命名:
和数组指针一样得写在*旁边。
接下来利用typedef优化上面的那个函数的声明:void (*signal(int, void(*)(int)))(int);它的参数含有一个函数指针,返回类型也是一个函数指针类型,那就先对函数指针类型进行重命名:typedefvoid(*pf_t)(int),所以这个函数指针的新名字就叫做pf_t了。所以代回到这个声明里面就是:pf_t signal(int, pf_t);简化完成。
补充一下:重命名时也可以用#define,但是两者是有区别的:当创建一个变量时,两者效果一致
但是连续创建两个变量的时候就有区别了:
#define的*没有与后面连起来,p4是个整型变量。
5.函数指针数组:类比一下,整型数组是存放整型的数组,字符数组是存放字符的数组,那么函数指针数组就是存放函数指针的数组了。假设给四个可以分别实现实数四则运算的函数,
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;
}
int main()
{
return 0;
}
可以发现这四个函数除了运算内容不一样,其它好像是一样的,可以先利用函数指针给它存起来,
四个函数指针类型一模一样,那么就可以创建一个数组放这些函数指针。
正确创建函数指针数组: 在原来函数指针的基础上再加上名字和[ ],就是int (*pfarr[4])(int, int) = {Add,Sub,Mul,Div};这样子数组内每一个元素存的都是对应函数的地址,所以只要进行和函数指针类似的操作再利用循环就可以通过这个数组访问里面的函数了,就比如说我想以此完成10和5的加减乘除运算:
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;
}
int main()
{
int (*pf1)(int,int) = Add;
int (*pf2)(int,int) = Sub;
int (*pf3)(int,int) = Mul;
int (*pf4)(int,int) = Div;
int (*pfarr[4])(int, int) = { Add,Sub,Mul,Div };
int i = 0;
for (i = 0; i < 4; i++)
{
int ret = pfarr[i](10, 5);
printf("%d\n", ret);
}
return 0;
}
总结梳理一下:我们首先学了一级指针,然后就是二级指针,再往后就是数组指针,函数指针,还有属于数组的指针数组和函数指针数组。char* p就是一级指针,char**pp就是二级指针,存放一级指针的。int arr[5]这种是数组,然后用来指向数组的指针就是数组指针int (*p)[5]=&arr。char* test(char* s,int n)这种就是函数,返回类型是指针,那么用来指向函数的就是函数指针char*(*p)(char*,int)=test。这时候想把指针存放到数组中就是指针数组char*arr[5]这样子的,如果是要放函数指针的话就先写想要的函数指针然后在名字那里后面加[ ],char*(*parr[ ])(char*,int n)。
以上就是本节内容了。