C陷阱与缺陷 第3章 语义“陷阱” 3.1 指针与数组

本文详细探讨了C语言中数组和指针的关系,指出数组在本质上是通过指针进行操作的。强调了指针加法的含义与二进制加法的区别,并解释了如何通过指针遍历和操作一维及二维数组。文中还举例说明了数组作为函数参数时的转换规则,以及如何声明和使用指向数组的指针。
摘要由CSDN通过智能技术生成

    语义“陷阱” 
    程序也有可能表面看上去是一个意思,而实际上的意思却相差甚远。 
    如果只是肤浅地考察,一切都“显得”合情合理,而事实上这种情况在所有C语言实现中给出的结果都是未定义的。在某些C语言实现中能够正常工作,而在另一些C语言中又不能工作的这种情况,属于可移植方面的问题。 

    指针与数组
    C语言中的数组值得注意的地方有以下两点。
    1、C语言中只有一维数组,而且数组的大小必须在编译器就作为一个常量确定下来。不过,C语言中的数组可以是任何类型的对象,当然也可以是另外一个数组。这样,要“仿真”出一个多维数组就不是一件难事。
    译注:C99标准允许变长数组(VLA)。GCC编译器中实现了变长数组,但细节与C99标准不完全一致,感兴趣的读者可以查看ISO/IEEC 9899:1999标准6.7.5.2节,以及Dennis M.Ritchie的Variable-Size Arrays in C。
    2、对于一个数组,我们只能做两件事:确定该数组的大小以及获得指向该数组下标为0的元素的指针。其他有关数组的操作,哪怕它们咋看上去是以数组下标进行运算的,实际上都是通过指针进行的。换句话说,任何一个数组下标运算都等同于一个对应的指针运算,因此我们完全可以依赖指针行为定义数组下标的行为。

    需要注意的特别之处的是,编程人员应该具备将数组运算与它们对应的指针运算融会贯通的能力,在思考有关问题时大脑中对这两种运算能够自如切换、毫无停滞。许多程序设计语言中内建有索引运算,在C语言中,索引运算是以指针算术的形式来定义的。
    声明一个数组:例如: 
    int a[3];
    语句声明了a是一个拥有3个整型元素的数组。类似地 
    struct {
        int p[4];
        double x;
    } p[17];
    声明了b是一个拥有17个元素的数组,其中每个元素都是一个结构,该结构包括了一个拥有4个整型元素的数组(命名为p)和一个双精度类型的变量(命名为x)。 
    int calendar[12][31];
    calendar是一个数组,该数组拥有12个数组类型的元素,其中每个元素都是一个拥有31个整型元素的数组(而不是一个拥有31个数组元素的数组,其中每个元素又是一个拥有12个整型元素的数组)。因此,sizeof(calendar)的值是372(31 * 12)与sizeof(int)的乘积。
    如果calendar不是用于sizeof的操作数,而是用于其他场合,那么calendar总是被转换成一个指向calendar数组的起始元素的指针。

    任何指针都是指向某种类型的变量。 
    int *ip;
    ip是一个指向整型变量的指针。 
    int i;
    把整型变量i的地址赋给指针ip,就像下面这样: 
    ip = &i;  //pointer ip point to int i
    给*ip赋值,就能够改变i的取值: 
    *ip = 17; //modify the value pointed to by pointer ip
    如果一个指针指向的数组中的一个元素,那么我们只要给这个指针加1,就能够得到指向该数组中下一个元素的指针。对于除1之外的其他整数情形,依次类推。
    给一个指针加上一个整数,与给该指针的二进制加上同样的整数,两者的含义截然不同。如果ip指向一个整数,那么ip+1指向的是计算机内存中的下一个整数,在大多数现代计算机中,它都不同于ip所指向地址的下一个内存位置。
    如果两个指针指向的是同一个数组中的元素,我们可以把这两个指针相减。这样做是有意义的。例如: 
    int *q = p + i;  
    那么我们可以通过q-p而得到i的值。值得注意的是,如果p和q指向的不是同一个数组中的元素,即使它们所指向的地址在内存中的位置正好间隔一个数组元素的整数倍,所得的结果仍然是无法保证其正确性的。
    如果我们在所有出现指针的地方,却采用了数组名来替换,那么数组名就被当做指向该数组下标为0的元素的指针。因此我们可以这样写: 
    p = a;  
    就会把数组中下标为0的元素的地址赋值给p。注意,这里我们并没有写成: 
    p = &a; //can't compile
    这种写法在ANSI C中是非法的,因为&a是一个指向数组的指针,而p是一个指向整型变量的指针,它们的类型不匹配。大多数早期版本的C语言实现中,并没有所谓“数组的地址”这一概念,因此&a要么被视为非法,要么就等于a。
    p指向数组a中下标为1的元素,可以这样写: 
    p = p + 1; 
    当然,该语句完全等同于下面的写法: 
    p++;    
    除了a被用作运算符sizeof的参数这一情形,在其他所有情况下a都代表指向数组a中下标为0的元素的指针。sizeof(a)的结果是整个数组a的大小,而不是数组a的元素的指针的大小。
    *a即数组a中下标为0的元素的引用。例如,我们可以这样写:
    *a = 84; 
    这个语句将数组a中下标为0的元素的值设置为84。概而言之,*(a+i)即数组a中下标为i的元素的引用;这种写法是如此常用,因此被简记为a[i]。
    实际上,由于a+i和i+a的含义一样,因此a[i]与i[a]具有同样的含义。但我们绝对不推荐后一种写法。

    “二维数组”,它实际上是以数组为元素的数组。尽管我们可以完全依据指针编写操纵一维数组的程序,而且这样做的一维情形下并不困难,对于二维数组,从记法上的便利性来说,采用下标形式就几乎是不可替代的了。 
    int calendar[12][31];
    int *p;
    int i;
    calendar[4]是calendar数组的第5个元素,是calendar数组中12个有着31个整型元素的数组之一。因此,calendar[4]的行为也就表现为一个有着31个整型元素的数组的行为。例如,sizeof(calendar[4])的结果是31与sizeof(int)的乘积。
    又如: 
    p = calendar[4];
    指针p指向了数组calendar[4]中下标为0的元素。 
calendar[4]是一个数组,可以通过下标的形式来指定这个数组中的元素,就像下面这样: 
    i = calendar[4][7];
    这个语句可以写成下面这样而表达的意思保持不变: 
    i = *(calendar[4] + 7);
    还可以进一步写成: 
    i = *(*(calendar + 4) + 7);
    从这里我们不难发现,用带方括号的下标形式明显要比完全用指针来表达简便得多。
    下面我们再看:
    p = calendar; //can't compile;
    这个语句是非法的。因为calendar是一个二维数组,即“数组的数组”,在此处的上下文中使用calendar名称会将其转换为一个指向数组的指针;而p是一个指向整型变量的指针,这个语句试图将一种类型的指针赋值给另一种类型的指针,所以是非法的。
    声明指向数组的指针。 
     int (*ap)[31];
    声明*ap是一个拥有31个整型元素的数组,因此ap就是一个指向这样的数组的指针。因而,我们可以这样写:
    int calendar[12][31]; 
    int (*monthp)[31];
    monthp = calendar;
    这样,monthp将指向数组calendar的第一个元素,也就是数组calendar的12个有着31个元素的数组类型元素之一。 
    清空calenar数组: 
    int month;
    for (month = 0; month < 12; month++) {
        int day;
        for (day = 0; day < 31; day++) {
            calendar[month][day] = 0;
        }
    }
    calendar[month][day] = 0;
    表示为 
    *(*(calendar + month) + day) = 0;
    但是真正有关的部分是哪些呢?
    使用指针monthp以步进的方式遍历数组calendar:
    int (*monthp)[31]; 
    for (monthp = calendar; monthp < &calendar[12]; monthp++) {
        /*处理一个月份的情况*/ 
    }
    向处理其他数组一样,处理指针monthp所指向的数组的元素:
    int (*monthp)[31]; 
    for (monthp = calendar; monthp < &calendar[12]; monthp++) {
        int *dayp;
        for (dayp = *monthp; dayp < &(*monthp)[31]; dayp++) {
            *dayp = 0;
        }
    }

    #include <stdio.h>
    #include <stdlib.h> 

    int main() {
        int (*monthp)[31]; 
        int calendar[12][31] = {0, 1, 2};    
        int i = 1;
        printf("i = %d\n", i);
        for (monthp = calendar; monthp < &calendar[12]; monthp++) {
            int *dayp;
            for (dayp = &(*monthp)[0]; dayp < &(*monthp)[31]; dayp++) {
                printf("%d ", *dayp);
            }
            printf("\n");
        }
        for (monthp = calendar; monthp < &calendar[12]; monthp++) {
            int *dayp;
            for (dayp = &(*monthp)[0]; dayp < &(*monthp)[31]; dayp++) {
                *dayp = 0;
            }
        }
        printf("i = %d\n", ++i);
        for (monthp = calendar; monthp < &calendar[12]; monthp++) {
            int *dayp;
            for (dayp = &(*monthp)[0]; dayp < &(*monthp)[31]; dayp++) {
                printf("%d ", *dayp);
            }
            printf("\n");
        }
        for (monthp = calendar; monthp < &calendar[12]; monthp++) {
            int *dayp;
            for (dayp = &(*monthp)[0]; dayp < *monthp + 31; dayp++) {
                *dayp = 1;
            }
        }
        printf("i = %d\n", ++i);
        for (monthp = calendar; monthp < &calendar[12]; monthp++) {
            int *dayp;
            for (dayp = &(*monthp)[0]; dayp < &(*monthp)[31]; dayp++) {
                printf("%d ", *dayp);
            }
            printf("\n");
        }
        printf("i = %d\n", ++i);
        for (monthp = calendar; monthp < &calendar[12]; monthp++) {
            int *dayp;
            for (dayp = (*monthp); dayp < &(*monthp)[31]; dayp++) {
                printf("%d ", *dayp);
            }
            printf("\n");
        }
    
        return EXIT_SUCCESS;
    }

输出:

i = 1
0 1 2 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
i = 2
0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
i = 3
1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1
1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1
1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1
1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1
1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1
1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1
1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1
1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1
1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1
1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1
1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1
1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1
i = 4
1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1
1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1
1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1
1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1
1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1
1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1
1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1
1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1
1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1
1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1
1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1
1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1

--------------------------------
Process exited after 0.1961 seconds with return value 0
请按任意键继续. . .

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

weixin_40186813

你的能量无可限量。

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值