指针数组,数组指针,函数指针,函数指针数组 ,指向函数指针数组的指针

首先有个问题:指针和数组有什么关系呢?
答案:什么关系都没有。
指针就是指针,在32位平台下,永远占4个字节,其值为某一个内存的地址。

数组就是数组,其大小与元素的类型和个数有关。定义数组时必须指定其元素的类型和个数。

1.指针数组

指针数组:首先它是一个数组,数组的元素都是指针,数组占多少个字节由数组本身决定。它是“储存指针的数组”的简称。

int *a[10];//指针数组,“[]”的优先级比“*”要高,这是一个数组,其包含10个指向int类型数据的指针

即变量名与哪一标识符先结合,即为哪种类型。

2.数组指针

数组指针:首先它是一个指针,它指向一个数组。在32位系统下永远是占4个字节,它是“指向数组的指针”的简称。

int(*p)[10];
//解释:p先和*结合,说明p是一个指针变量,然后指着指向的是一个大小为10个整型的数组。所以p是一个指针,指向一个数组,叫数组指针
那数组的地址如何来存储?
int arr[10] = {0};
int *p1 = &arr;//错误,&arr 类型为数组指针,p1的类型为int*,类型不匹配
int (*p2)[10] = &arr;//正确,数组指针

补充:若所指向大小不同,则类型也不同,如:
int (*p2)[2]=&arr;//错误

3.函数指针

首先来了解数组和指针是如何传参的?
函数本身是没有类型的,只有函数的返回值才有类型

a.数组传参
数组传参时,编译器将其解析为指向其首元素首地址的指针。
实际传递数组的大小与形参指定数组的大小无关。

b.指针传参
指针传参时创建了临时变量。

当一个函数的参数部分为一级指针的时候,函数能接收什么参数? 
比如:
void test1(int *p)
{}
//test1函数能接收什么参数?

//答案是:若int a[]={1,2};
//则能接受 &a,a[],*a;

举个栗子吧:看看有什么错误?

void GetMemory(char * p, int num)
{
    p = (char *)malloc(num*sizeof(char));
}
int main()
{
    char *str = NULL;
    GetMemory(str10);
    strcpy(str,”hello”);
    free(str);//free并没有起作用,内存泄漏
    return 0;
}

原因:在运行strcpy(str,”hello”)语句的时候发生错误。这时候观察str的值,发现仍然为NULL。也就是说str本身并没有改变,我们malloc的内存的地址并没有赋给str,而是赋给了_str。而这个_str是编译器自动分配和回收的,我们根本就无法使用。

那么如何解决呢?

解决方案:
第一:用returnchar * GetMemory(char * p, int num)
{
    p = (char *)malloc(num*sizeof(char));
    return p;
}
int main()
{
    char *str = NULL;
    str = GetMemory(str10);
    strcpy(str,”hello”);
    free(str);
    return 0;
}

这样就复制成功了!
还有一个方法:
用二级指针。
void GetMemory(char ** p, int num)
{
    *p = (char *)malloc(num*sizeof(char));
    return p;
}
int main()
{
    char *str = NULL;
    GetMemory(&str10);
    strcpy(str,”hello”);
    free(str);
    return 0;
}
    注意,这里的参数是&str而非str。这样的话传递过去的是str的地址,是一个值。所以malloc分配的内存地址是真正赋值给了str本身。

接下来进入主题:
c.函数指针

顾名思义,函数指针就是函数的指针。它是一个指针,指向一个函数。

我们用个代码来说明吧:


void test()
{
printf("hehe\n");
}
//下面pfun1和pfun2哪个有能力存放test函数的地址?
void (*pfun1)();
void *pfun2();

答案:pfun1可以存放。pfun1先和*结合,说明pfun1是指针,指针指向的是一个函数,指向的函数无参数,返回值类型为void

来看个有趣的例子吧:
((void() ())0)()——这是什么?

乍一看,也太复杂了吧,what is it?

没有发狂吧?下面我们就来分析分析:
第一步:void(*) (),可以明白这是一个函数指针类型。这个函数没有参数,没有返回值。
第二步:(void(*) ())0,这是将0强制转换为函数指针类型,0是一个地址,也就是说一个函数存在首地址为0的一段区域内。
第三步:((void() ())0),这是取0地址开始的一段内存里面的内容,其内容就是保存在首地址为0的一段区域内的函数。
第四步:((void() ())0)(),这是函数调用。

4.函数指针数组

char * (*pf[3])(char * p);

这是定义一个函数指针数组。它是一个数组,数组名为pf,数组内存储了3个指向函数的指针。这些指针指向一些返回值类型为指向字符的指针、参数为一个指向字符的指针的函数。

函数指针数组怎么使用呢?

#include <stdio.h>
#include <string.h>
char *fun1(char *p)
{
     printf("%s\n",p);
     return p;
}
char *fun2(char *p)
{
     printf("%s\n",p);
     return p;
}
char *fun3(char *p)
{
    printf("%s\n",p);
    return p;
}
int main()
{
    char *(*pf[3])(char *p);
    pf[0] = fun1; //可以直接用函数名
    pf[1] = &fun2; //可以用函数名加上取地址符
    pf[2] = &fun3;
    pf[0]("fun1");
    pf[0]("fun2");
    pf[0]("fun3");
    return 0;
}

这里写图片描述

5.函数指针数组的指针

看着这个标题没发狂吧?反正我是不好了…..
指向函数指针数组的指针是一个 指针 .
指针指向一个 数组 ,数组的元素都是 函数指针 ;

char *(*(*pf)[3])(char *p);


分析一下:
pf这个指针指向一个包含了3个元素的数组;这个数组里面存的是指向函数的指针;这些指针指向一些返回值类型为指向字符的指针、参数为一个指向字符的指针的函数。

说完了上面这些概念问题,接下来据多个例子讲解吧:

练习1:

intmain()
{
    int a[4] = { 1, 2, 3, 4};
    int *ptr1 = (int*)(&a + 1);
    int *ptr2 = (int*)((int)a + 1);
    printf( "%x,%x", ptr1[-1], *ptr2);
    return 0;
}

讲解:
ptr1:将&a+1的值强制转换成int*类型,赋值给int* 类型的变量ptr,ptr1肯定指到数组a的下一个int类型数据了。ptr1[-1]被解析成*(ptr1-1),即ptr1往后退4个byte。所以其值为0x4。
ptr2:(int)a+1的值是元素a[0]的第二个字节的地址。然后把这个地址强制转换成int*类型的值赋给ptr2,也就是说*ptr2的值应该为元素a[0]的第二个字节开始的连续4个byte的内容。
其内存布局如下图:

这里写图片描述

练习2:

int main()
{
    int a[5] = { 1, 2, 3, 4, 5};
    int *ptr = (int *)(&a + 1);
    printf( "%d,%d", *(a + 1), *(ptr - 1));//输出2,5
    return 0;
}
//程序的结果是什么?

练习3

structTest
{
    int Num;
    char *pcName;
    short Date;
    char cha[2];
    short Ba[4];
}*p;
假设p 的值为0x100000。 如下表表达式的值分别为多少?
p + 0x1= 0x___ ?
(unsigned long)p + 0x1= 0x___ ?
(unsigned int*)p + 0x1= 0x___ ?

解析:p + 0x1= 0x100014;
p+0x1的值为0x100000+sizof(Test)*0x1。至于此结构体的大小20byte。
(unsigned long)p + 0x1=0x100001;
这个表达式其实就是一个无符号的长整型数加上另一个整数。
(unsigned int*)p + 0x1= 0x100004;
p被强制转换成一个指向无符号整型的指针;

练习4:
注意有坑哦!

#include <stdio.h>
int main(int argc, char * argv[])
{
    int a[3][2] = { (0, 1), (2, 3), (4, 5) };//花括号内为小括号,逗号表达式,相当于int a [3][2]={ 1, 3,5};
    int *p;
    p = a[0];
    printf( "%d", p[0]);//输出 1,3,5
}

练习5:

int main()
{
    int a[5][5];
    int (*p)[4];//p是指向一个包含4个元素的数组的指针
    p = a;
    printf( "a_ptr=%#p,p_ptr=%#p\n", &a[4][2], &p[4][2]);
    printf( "%p,%d\n", &p[4][2] - &a[4][2], &p[4][2] - &a[4][2]);
    return 0;
}

这里写图片描述

这里写图片描述

那么 FFFFFFFC是什么呢:
-4原码:
1000 0000 0000 0000 0000 0000 0000 0100
反码
1111 1111 1111 1111 1111 1111 1111 1100
F F F F F F F C

练习5:

int main()
{

    char *c[] = { "ENTER", "NEW", "POINT", "FIRST" };
    char **cp[] = { c + 3, c + 2, c + 1, c };
    char ***cpp = cp;
    printf("%s\n", **++cpp);
    printf("%s\n", *--*++cpp + 3);
    printf("%s\n", *cpp[-2] + 3);
    printf("%s\n", cpp[-1][-1] + 1);


    system("pause");
    return 0;
}

解析:

这里写图片描述

这里写图片描述

如果还有不懂可参考《c语言深度解剖》这本书!

以上是我的总结,请多指教!

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值