您的位置:首页 > 编程语言 > C语言/C++

c语言之指针-指针函数和函数指针与指针数组和数组指针

2020-04-05 12:16 477 查看

1.函数指针和指针函数的概念
(1)之前在上篇文章中说到int (*pArr)[10]为数组指针,而函数指针和数组指针类似,形如int (*pFun)(int,int)这种就叫做函数指针。
函数指针和函数是有区别的,实质上它是一个指针,所以(*pFun)必须要有(*)。函数指针也叫做指向函数的指针。
(2)指针函数
int *fun(int a,int b)
{
    int* p=&a;
    *p += a+b;
    return p;    
}
形如int *fun(int a, int b);为原型的就叫指针函数,毫无疑问,它是一个函数,而函数的返回值是一个指针(int *)类型。
所以我们把这种返回值是指针类型的函数叫做指针函数。

2.函数指针的使用
int Add(int a,int b)
{
    return a+b;
}
//int (*pfun)(int,int);    
typedef int (*pFun)(int,int); 

int main()
{
    int res;
    // 对于int (*pfun)(int,int)
    pfun=&Add;
    res = (*pfun)(10,20);
    printf("%d\n",res);

    // 对于typedef int (*pFun)(int,int)
    pFun p=&Add;
    res = (*p)(10,20);
    printf("%d\n",res);

    return 0;
}
输出结果:
30
30
(1)对函数指针使用typedef
因为函数指针在很多时候形式较为复杂,一般来说我们用typedef来对函数指针的类型重定义。
如果我们不使用typedef,那么int (*pfun)(int,int)相当于定义了一个变量pFun。每次定义变量时类型都要如此显得很复杂。
而typedef int (*pFun)(int,int);相当于声明了pFun是一个int (*)(int,int)类型,因此我们可以用pFun直接来定义一个函数指针的变量p。
(2)如何操作函数指针
函数指针和普通指针一样,可以用来指向一个对象,不过函数指针是指向一个函数的,所以pfun=&Add相当于函数指针指向了函数。
和普通对象一样,*pfun就是所指对象的值(常称解引用),所以(*pfun)(10, 20)相当于调用了Add(10,20),*和pfun一体的。
(3)函数指针指向函数时的一些
其实上述pfun=&Add这个函数指针pfun指向函数Add,也可以换成pfun=Add或者直接*pfun=Add甚至是*pfun=&Add(一般编译器会有警告)。
我们观察内存中的地址,本机上通过debug找到Add的地址是0x00401005,&Add的地址也是0x00401005。就是说Add和&Add的内存地址相同。
其实pfun指向函数名,函数名和数组名类似,只是进入到这个函数的入口地址而已。所以我们用pfun指向了Add和&Add都相当于指向0x00401005空间的内容。
而函数指针的类型规定了指针从函数Add的入口地址开始为该指针划分内存空间的大小(就是int (*)(int,int)的大小),所以首地址和空间大小确定了。
这也就是为什么pfun指向Add和将Add直接赋值给pfun的效果相同。

3.函数指针和指针函数的结合使用
int Add(int a,int b)
{
    return a+b;
}

typedef int (*pFun)(int,int); 

pFun printSum(int a,int b, pFun pfun)
{
    printf("The sum is=%d\n",pfun(a,b));
    return pfun;    
}

int main()
{
    printSum(10,20,Add);
    return 0;
}
(1)再次理解typedef带来的好处
前面说过,我们应该对函数指针使用typedef,这样在定义变量的时候更加方便。而且在函数指针作为函数的参数或返回值的时候这一点尤为明显。
typedef int (*pFun)(int,int); 
pFun printSum(int a,int b, pFun pfun);
对于这两句代码,如果我们不使用typedef的话后果如何:
定义一个int (*pfun)(int,int); 然而printSum函数声明中的类型也要用函数指针。那好,让我们看看printSum的函数原型结构。
printSum具有三个参数,分别是int,int,和int (*)(int,int)类型,返回值类型也是int (*)(int,int)。
那么printSum函数原型应该是int (*)(int,int) printSum(int a,int b, int (*pfun)(int,int));这个时候build的时候编译错误。
正确printSum的原型应该是int (*printSum(int a, int b, int (*pfun)(int,int)))(int,int);外层的函数指针先结合指针,再结合函数参数。
这种形式的函数并非返回值为int,而是函数指针,看起来很奇怪。后面我们再来分析这种结合指针的复杂式。
(2)函数指针作为函数参数
这是一种很常用的c/c++用法,一般称作为函数参数的函数指针为回调函数,其他变成语言大多没有指针,但是有回调函数。
一般函数在调用时只能传入自己参数按照该函数的代码顺序执行,但是函数指针可以使得在调用函数的时候调用的函数再调用你自己传入的函数。
这样就保证了程序的更加灵活性,特别是调用库函数和一些算法的时候,常常我们加入函数指针作为参数来人为规定函数的走向。
回调函数在事件系统和消息处理(通知)的时候也扮演重要角色。
(3)返回指针值的函数
指针函数即返回指针值的函数,pFun是一个函数指针,那么函数printSum就是一个返回函数指针的函数(指针函数指针)。
同样,我们在使用变量接收这个返回的地址的时候也必须要用指针值来接受,这里是函数指针。关于返回函数指针,我暂时不了解它有什么实际意义。

4.指针和数组的关系
int ar[10]={1,2,3,4,5,6,7,8,9,10};
int *p=ar;
int (*pp)[10]=&ar;            
//int **pp=&ar;                // 错误

printf("p[1]=%d\n",p[1]);
printf("*(p+1)=%d\n", *(p+1));
printf("(*pp)[1]=%d\n", (*pp)[1]);

printf("%p\n",&ar[0]);
printf("%p\n",ar);            
printf("%p\n",&ar);            
printf("%d\n",sizeof(ar));
printf("%d\n",sizeof(&ar[0]));
printf("%p\n",sizeof(&ar));

printf("p=%p\n", p);
printf("p+1=%p\n", p+1);
printf("pp=%p\n", pp);
printf("pp+1=%p\n", pp+1);
输出结果:
p[1]=2
*(p+1)=2
(*pp)[1]=2
0019FF08
0019FF08
0019FF08
40
4
00000028
p=0019FF08
p+1=0019FF0C
pp=0019FF08
pp+1=0019FF30
(1)数组指针和二级指针
c语言一直流传着数组就是指针的说法,这种说法是不正确的。指针是存在一个合法性的问题,而数组不同。
上章中说到可以用指针*p来操作ar,也可以用(*p)[10]来指向ar,这个时候p[1]和*(p+1)结果相同,同样(*(pp+0))[1]和*(pp[0]+1)相同。
看似[]和*可以互相转化,但是int **pp=&ar却是错误的。因为只有*p具有指针的功能,int *现在只是类型,第二级的指针已经不具有指向的功能了。
(2)数组名和数组地址
int *p=ar;p的值就是数组名ar的值,而*p的值对应ar[0]的值。而p是*p的地址,那么ar是否是ar[0]的地址呢?
关于数组名就是数组首元素地址这种说法是错误的。&ar[0]和ar还有&ar三者的值都是一个地址值且相等,所以容易让人产生错觉。
我们可以用sizeof来分别测试&ar[0]和ar的大小,发现前者为40,后者为4。所以他们虽然同一地址,但是对于该值开辟的内存大小空间是不同的。
和指针类似,ar相当于是一个首地址,但是他的类型规定了他有10个int(40bytes)和内存空间。而ar[0]就是简单的一个int类型,4字节。
(3)从数组名的角度再看指针
指针是带有类型的,对于int *p和int (*pp)[10]都可以操作数组ar,而数组名ar的所占空间是40bytes。
p=ar,p存储ar的值,但是我们发现p+1只偏移了4bytes,所以p真正指向的是ar[0],而p的值就是&ar[0]并非数组名,所以p+1就是&p[1],*(p+1)就是p[1]。
int (*pp)[10]=&ar,这个时候pp+1偏移了40bytes,所以pp的类型应该是int (*)[10],(*(pp+1))[0]指向了数组ar之外的下一个空间单元。
正确的通过数组指针寻数组值ar[1]的方式是(*pp)[1],但是由于数组下标和指针寻值效果一样。
所以(*pp)[1]也可以用*((pp[0])+1)或者*((*pp)+1)甚至pp[0][1]。他们效果一样,而pp[0][1]也是二维数组的形式。这个特性也衍生出来更多问题。

4.指针数组
int main(int argc, char *argv[])                // argv就是指针数组
{
    int ar[3]={1,2,3};
//    int (*p)[4]=&ar;            // 错误

    int a=10;
    int b=20;
    int c=30;
    int *A[30]={&a,&b,&c};        

    char *B[3]={"C","C++","JavaScript"};                

    return 0;
}
(1)数组指针和指针数组的区别
从字面上来看数组指针就是指向数组的指针,指针数组就是数据成员为指针的数组。一个是指针,另一个是指针的数组。
对于int ar[3],只能用int (*p)[3]来指向它,int (*p)[4]来指向它就是错误的。所以数组指针的下标号和数组的下标号必须对应相等。
而对于指针数组int *A[30],它的下标号大小并不需要和数据数据的大小对应(数组的特性决定的)。
因为[]的优先级高于*,所以在定义一个数组指针和指针数组,只需要对*使用()即可,int (*A)[3]就是数组指针,int *A[3]就是指针数组。
(2)指针数组的使用和意义
int *A[30]={&a,&b,&c};可以将变量的地址(指针值)存储到一个数组里,数组A存放了三个指针数据。
而对于字符串char*来说,它同样是一个char的指针,所以可以直接存放可变长度的字符串到char指针数组中,这样更加方便灵活。
在有很多条记录的时候,例如n个学生的学号和姓名,我们可以用指针数组单独存放n个学号,另一个指针数组存放n个姓名。
(3)main函数的参数
每一个c程序中都有main函数,他是程序的入口函数,cpu从main开始执行c语言程序。通常我们的main函数的形式是void main(){ ... }。
其实main函数也是带参数的,在传统的单任务系统中使用命令行来运行c程序,main函数的原型是int main(int argc, char *argv[])。
如果通过命令行运行了c程序,我们可以在main.c后面加上想要传入给main函数的参数,main就可以直接使用我们传入的参数。
argc是传入字符串的个数,argv是传入字符串的内容。argv也可以是char **类型。程序运行结束时return 0返回给命令行界面,一般代表函数正常结束。
其实一般在传入参数的时候,即使函数参数的数组规定了下标号,这个下标号也没有意义,所以通常我们传入一个int值来规定数组大小。

  • 点赞 1
  • 收藏
  • 分享
  • 文章举报
摇摆yaoyaobaibai 发布了3 篇原创文章 · 获赞 3 · 访问量 933 私信 关注
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: 
相关文章推荐