指针和数组(《C语言深度解剖》——读书小结)

目录:

(1)数组?指针?

书中一开始提出这样三个问题:
(1)什么是数组?
(2)什么是指针?
(3)数组和指针之间有何关系?
回答第一个问题:
数组是用于储存多个相同类型数据的集合, 若将有限个类型相同的变量的集合命名,那么这个名称为数组名。
回答第二个问题:
变量、常量、自定义类型甚至函数的创建都在内存中占用一定空间的大小,而指针用于存放该空间起始位置的地址。
回答第三个问题:
回答这个问题先来看两段代码

#include <stdio.h>

int main()
{
    int arr[10] = {1,2,3,4,5,6,7,8,9,0};
    int i = 0;
    for (i=0; i<10; i++)
    {
        printf("%d ", arr[i]);
    }
    return 0;
}

#include <stdio.h>

int main()
{
    int arr[10] = {1,2,3,4,5,6,7,8,9,0};
    int i = 0;
    int *p = arr;
    for (i=0; i<10; i++)
    {
        printf("%d ", *(p+i));
    }
    return 0;
}

都完成了循环遍历整个数组,如果你现在认为数组等于指针,或者数组和指针有一定的关系那就大错特错了

书中明确指出,数组和指针之间没有任何关系

  • 指针就是指针,指针变量在 32 位系统下,永远占 4 个 byte,其值为某一个内存的地址。 指针可以指向任何地方,但是不是任何地方你都能通过这个指针变量访问到。
  • 数组就是数组,其大小与元素的类型和个数有关。定义数组时必须指定其元素的类型 和个数。数组可以存任何类型的数据,但不能存函数

上面的两段代码只是数组的两种不同的访问格式:

  • 以下标形式访问

  • 以指针形式访问

两种访问格式本质上没有区别,在编译器看来是完全相同的两种书写格式。
所以就引出了另一个问题,既然是两种书写格式,为什么不统一起来。

(2)数组和指针的区别(两种形式并存的必要)

书中引入了两个概念:

  • 完全匿名访问

  • 具名+匿名访问

何出此言,原因在于若定义两个变量

char *p = "abcdef";
char a[]= "123456";

p是一个指针变量,存放的是在文字常量区创建的"abcdef"的首元素地址,这块内存没有名字,所以不管以何种形式访问,在编译器看来都是通过首元素地址+偏移量来访问,故为完全匿名访问

数组a的创建在栈上,把整个数组创建的7byte大小空间称为数组a,a是这块内存的名字,在其访问内存中,在编译器看来有一个解引用的过程,此时a降级为数组的首元素地址,然后再通过偏移量来访问

所以不管是完全匿名访问还是具名+匿名访问取决于访问对象,与访问形式无关

再者若统一了两种书写格式,将会在以下的场景中出现问题:

(2.1)定义为指针,声明为数组

定义成什么,然后做相应声明程序必然无误

若在一个源文件中将字符串常量定义成指针

char *p = "abcdef";

在另一个源文件中声明成数组后并使用

#include <stdio.h>

extern char p[];

 int main()
 {
    printf("%s\n", p);//输出错误
    printf("%s\n", *(int*)p);//输出正确

    return 0;
 }

这里写图片描述
如图所示,第一个输出的值由声明的数组p决定,它找到了存放"abcdef"首元素地址的那块内存(4byte)的起始地址,然后将p[0]、p[1]、p[2]存储整形数字ASCLL码对应字符输出了,地址小端存储,大多数情况下地址均在0x00xxxxxx区域,所以找到了p[3]即当作\0停了下来

如果想正确输出,改变指针的访问权限,强制类型转换为int *后解引用,刚好拿出"abcdef"的首元素地址

(2.2)定义为数组,声明为指针

如果反过来呢,会不会出错?又是如何出错的?
若在一个源文件中将字符串常量定义成数组

char arr[] = "abcdef";

在另一个源文件中声明成指针后使用

#include <stdio.h>

extern char* arr;

 int main()
 {
    printf("%s\n", arr);//程序崩溃
    printf("%s\n", &arr);//输出正确

    return 0;
 }

这里写图片描述
定义为数组,声明为指针,此时的“指针”名其访问权限是4byte,得到一块已知的非法地址,访问其程序必然崩溃

一般情况下我们只考虑指针变量存放的地址,但指针在创建的时候也有空间,在32位系统下占4byte(不过我的电脑是64位操作系统,为何依然是4byte,而不是8byte,没搞懂望指点),这4byte的空间也有地址,而我们一般情况下不关心这个地址,但编译器关心。定义为数组,声明为指针,&“指针名”拿到了这4byte的起始地址,刚好就是数组名在做相关操作时降级处理的首元素地址,依然能输出定义的字符串

经过上面的分析足以见得指针和数组没有关系,就连声明的时候也要做相应声明,否则将会自掘坟墓

接下来就讲到了几个比较拗口的名字,但说白了就是不同类型的指针或者数组而已,抓住上面指针和数组的定义就错不了

(3)指针数组

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

 int main()
 {
    char* arr[3] = {"hello", "bit", "world"};
    int i = 0;
    for(i=0; i<3; i++)
    {
        printf("%s\n", arr[i]);
    }
    return 0;
 }

如上代码,创建了一个拥有三个元素的数组arr,每个元素都是char *类型的指针,具体的值是"hello", "bit", "world"三个字符串的首元素地址,所以arr就被称为指针数组

(4)数组指针

  • 数组指针:首先它是一个指针,它指向一个数组。在 32 位系统下永远是占 4 个字节, 至于它指向的数组占多少字节,不知道。它是“指向数组的指针”的简称

指针的类型决定了指针的访问权限和步长,而数组指针的类型是int(*)[],指针+1跳过所指向数组的空间大小,通过+偏移量后解引用可以拿到数组中的元素

如下代码,一个二维数组在函数参数部分用一个数组指针接收,得到 arr元素的首元素首地址,arr[i][j]可以通过arr+i*sizeof(int)5+j*sizeof(int)。同样,可以换 算成以指针的形式表示:(*(arr+i)+j)

#include <stdio.h>

void print(int (*p)[5], int x, int y)
{
    int i = 0;
    int j = 0;
    for(i=0; i<x; i++)
    {
        for(j=0; j<y; j++)
        {
            printf("%d ", *(*(p+i)+j));
        }
        printf("\n");
    }
}

int main()
{
    int arr[3][5] = {1,2,3,4,5,5,4,3,2,1,6,7,8,9,0};
    print(arr, 3, 5);

    return 0;
}

二维数组进行传参,传的是首元素的地址,即第一行的地址,用于接收的函数参数应该有能力找到这个二维数组的每一行,所以定义成int(*)[5]类型

(5)函数指针

函数也有地址,如果需要一个变量存放函数的地址固然想到了函数指针

函数指针在定义的时候需要注意的是,必须指明函数的参数个数及类型

#include <stdio.h>

 int Add(int x, int y)
 {
    return x+y;
 }


 int main()
 {
    int (*p)(int, int) = &Add;
    printf("%d\n", (*p)(2,3));

    return 0;
 }

printf("%d\n", (*p)(2,3))(*p)取出存在这个地址上的函数,通过函数参数的调用形式完成一次函数调用

(6)函数指针数组

与指针数组类似,函数指针数组首先是一个数组,数组中的每一个元素都是一个函数的地址

#include <stdio.h>

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 (*pfun[4])(int, int) = {Add, Sub, Mul, Div};
    int i = 0;
    for(i=0; i<4; i++)
    {
        printf("%d\n", (*(*(pfun+i)))(9, 3));
    }
    return 0;
}

定义了一个函数指针数组,有4个元素,数组中每个元素分别存放了函数Add, Sub, Mul, Div的地址

如果想要利用上面的代码实现一个计算器:

(6.1)计算器常规实现
#include <stdio.h>

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;
}

void menu()
{
    printf("******************************\n");
    printf("**     1. add     2. sub    **\n");
    printf("**     3. mul     4. div    **\n");
    printf("**          0.exit          **\n");
    printf("******************************\n");
}

int main()
{
    int input = 0;
    int x = 0;
    int y = 0;
    int ret = 0;

    do
    {   
        menu();
        printf("请选择:>");
        scanf("%d", &input);

        switch(input)
        {
        case 1:
            {
                printf("请输入两个操作数:>");
                scanf("%d%d", &x, &y);
                ret = Add(x, y);
                printf("ret = %d\n", ret);
            }

            break;
        case 2:
            {
                printf("请输入两个操作数:>");
                scanf("%d%d", &x, &y);
                ret = Sub(x, y);
                printf("ret = %d\n", ret);
            }
            break;
        case 3:
            {
                printf("请输入两个操作数:>");
                scanf("%d%d", &x, &y);
                ret = Mul(x, y);
                printf("ret = %d\n", ret);
            }
            break;
        case 4:
            {
                printf("请输入两个操作数:>");
                scanf("%d%d", &x, &y);
                ret = Div(x, y);
                printf("ret = %d\n", ret);
            }
            break;
        case 0:
            printf("退出程序\n");
            break;
        default:
            printf("选择错误\n");
            break;
        }
    }while(input);
    return 0;
}
(6.2)计算器函数封装实现

为了避免在错误输出和选择退出时不提示输出不打印结果,必须在每一个正常选择的case语句中写入相同的代码块,所以代码显得十分冗余,有没有办法解决?我们首先想到了用函数来封装

#include <stdio.h>

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;
}

void menu()
{
    printf("******************************\n");
    printf("**     1. add     2. sub    **\n");
    printf("**     3. mul     4. div    **\n");
    printf("**          0.exit          **\n");
    printf("******************************\n");
}
void calc(int (*pfun)(int, int))
{
    int x = 0;
    int y = 0;
    int ret = 0;
    printf("请输入两个操作数:>");
    scanf("%d%d", &x, &y);
    ret = pfun(x, y);
    printf("ret = %d\n", ret);
}

int main()
{
    int input = 0;
    int x = 0;
    int y = 0;
    int ret = 0;

    do
    {   
        menu();
        printf("请选择:>");
        scanf("%d", &input);

        switch(input)
        {
        case 1:
            calc(Add);
            break;
        case 2:
            calc(Sub);
            break;
        case 3:
            calc(Mul);
            break;
        case 4:
            calc(Div);
            break;
        case 0:
            printf("退出程序\n");
            break;
        default:
            printf("选择错误\n");
            break;
        }
    }while(input);
    return 0;
}

用一个参数为函数指针的函数将重复的代码进行封装,目的是能够接收传递过来的函数地址

(6.3)计算器转移表实现

有没有更简单的实现方式?那就需要函数指针数组帮忙!

#include <stdio.h>

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;
}

void menu()
{
    printf("******************************\n");
    printf("**     1. add     2. sub    **\n");
    printf("**     3. mul     4. div    **\n");
    printf("**          0.exit          **\n");
    printf("******************************\n");
}

int main()
{
    int input = 0;
    int x = 0;
    int y = 0;
    int ret = 0;

    //转移表
    int (*pfun[5])(int , int) = {0, Add, Sub, Mul, Div};

    do
    {   
        menu();
        printf("请选择:>");
        scanf("%d", &input);
        if (input>=1 && input <=4)
        {
            printf("请输入两个操作数:>");
            scanf("%d%d", &x, &y);
            ret = pfun[input](x, y);
            printf("ret = %d\n", ret);
        }
        else if (!input)
        {
            printf("程序退出!\n");
            break;
        }
        else
        {
            printf("输入有误,重新输入!\n");
            continue;
        }
    }while(input);
    return 0;
}

转移表博客,非广告,供参考!

(7)指向函数指针数组的指针

指向函数指针数组的指针是一个指针
指针指向一个数组,数组的元素都是函数指针

拿上面的转移表(函数指针数组)来说明问题

int (*pfun[4])(int, int) = {Add, Sub, Mul, Div};

如果想要创建一个可以指向这个数组的指针,该如何书写

int (*(*ppfun)[4])(int, int);
ppfun = &pfun;

以上内容均属学习小结,供日后反刍,如有错误望指出,不胜感激!

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值