指针和数组分析

1 数组的本质

  • 数组是一段连续的内存空间。
  • 数组的空间大小为sizeof(array_type)*array_size。
  • 数组名可看作指向数组第一个元素的常量指针。

问题:
1. a + 1的意义是什么?结果是什么?
2. 指针运算的意义是什么?结果又是什么?

编程实验:a + 1的结果是什么?

#include <stdio.h>

int main()
{
    int a[5] = {0};
    int* p = NULL;

    printf("a = 0x%X\n", (unsigned int)(a));
    printf("a + 1 = 0x%X\n", (unsigned int)(a + 1));

    printf("p = 0x%X\n", (unsigned int)(p));
    printf("p + 1 = 0x%X\n", (unsigned int)(p + 1));

    return 0;
}

2 指针的运算

  • 指针是一种特殊的变量,与整数的运算规则为:
    • p + n < – > (unsigned int)p + n*sizeof(*p);
  • 结论:当指针p指向一个同类型的数组元素时,p + 1将指向当前元素的下一个元素;p – 1将指向当前元素的上一个元素。

  • 指针之间支持减法运算。

  • 参与减法运算的指针类型必须相同。
  • 减法的运算规则:
    • p1 – p2 < – > ((unsigned int)p1 – (unsigned int)p2) / sizeof(type)。

注意:
1. 只有当两个指针指向同一个数组中的元素时,指针相减才有意义,其意义为指针所指元素的下标差。
2. 当两个指针指向的元素不在同一个数组中时,结果未定义。

3 指针的比较

  • 指针也可以进行关系运算(<, <=, >, >=)。
  • 指针关系运算的前提是同时指向同一个数组中的元素(不同没有意义)。
  • 任意两个指针之间的比较运算(= =, !=)无限制。
  • 参与比较运算的指针类型必须相同(不同没有意义)。

实例分析:指针运算初探

#include <stdio.h>

int main()
{
    char s1[] = {'H', 'e', 'l', 'l', 'o'};
    int i = 0;
    char s2[] = {'W', 'o', 'r', 'l', 'd'};
    char* p0 = s1;
    char* p1 = &s1[3];
    char* p2 = s2;
    int* p = &i;

    printf("%d\n", p0 - p1); //ok, -3
    printf("%d\n", p0 + p2); //error
    printf("%d\n", p0 - p2); //error(没意义)
    printf("%d\n", p0 - p);  //error
    printf("%d\n", p0 * p2); //error
    printf("%d\n", p0 / p2); //error

    return 0;
}

实例分析:指针运算的应用

#include <stdio.h>

#define DIM(a) (sizeof(a) / sizeof(*a))

int main()
{
    char s[] = {'H', 'e', 'l', 'l', 'o'};
    char* pBegin = s;
    char* pEnd = s + DIM(s); // Key point
    char* p = NULL;

    printf("pBegin = %p\n", pBegin);
    printf("pEnd = %p\n", pEnd);

    printf("Size: %d\n", pEnd - pBegin);

    for(p=pBegin; p<pEnd; p++)
    {
        printf("%c", *p);
    }

    printf("\n");

    return 0;
}

小结

  • 数组声明时编译器自动分配一片连续的内存空间。
  • 指针声明时只分配了用于容纳地址值的4字节空间。
  • 指针和整数可以进行运算,其结果为指针。
  • 指针之间只支持减法运算,其结果为数组元素下标差。
  • 指针之间支持比较运算,其类型必须相同。

4 数组的访问形式

问题

数组名可以当作常量指针使用,那么指针是否也可以当作数组名来使用呢?

数组的访问方式

  • 以下标的形式访问数组中的元素:
int main()
{
    int a[5] = 0;
    a[1] = 3;
    a[2] = 5;
    return 0;
}
  • 以指针的形式访问数组中的元素:
int main()
{
    int a[5] = 0;
    *(a + 1) = 3;   
    return 0;
}

下标形式VS指针形式

  • 指针以固定增量在数组中移动时,效率高于下标形式。
  • 指针增量为1且硬件具有增量模型时,效率更高。
  • 下标形式与指针形式的转换:a[n] <–>*(a + n) <–> *(n + a) <–> n[a]

注意:现代编译器的生成代码优化率已大大提高,在固定增量时,下标形式的效率已经和指针形式相当;但从可读性和代码维护的角度来看,下标形式更优。

实例分析:数组的访问方式

#include <stdio.h>

int main()
{
    int a[5] = {0};
    int* p = a;
    int i = 0;

    for(i=0; i<5; i++)
    {
        p[i] = i + 1;
    }

    for(i=0; i<5; i++)
    {
        printf("a[%d] = %d\n", i, *(a + i));
    }

    printf("\n");

    for(i=0; i<5; i++)
    {
        i[a] = i + 10;
    }

    for(i=0; i<5; i++)
    {
        printf("p[%d] = %d\n", i, p[i]);
    }

    return 0;
}

编程实验:数组和指针的不同

// main.c
#include <stdio.h>

int main()
{
    extern int* a;

    printf("&a = %p\n", &a);
    printf("a = %p\n", a);
    printf("*a = %d\n", *a);

    return 0;
}

// extern.c
a[] = {1, 2, 3, 4, 5};

/* 分析:当编译器编译到extern int* a;这句话话的时候就会把a这个标识符当作一个指针看待。指针a的地址为就
是数组a标识符所在的地址,&a就是a所在的地址值,a就是里面保存的值(0x1),*a就是保存的地址值所对应的内存中
的值,因此会产生段错误。*/

5 a和&a的区别

  • a为数组首元素的地址。
  • &a为整个数组的地址。
  • a和&a的区别在于指针运算。
    • a + 1 <-> (unsigned int)a + sizeof(*a)
    • &a + 1 <-> (unsigned int)&a + sizeof(*&a) <-> (unsinged int)(&a) + sizeof(a)

实例分析:指针运算经典问题

#include <stdio.h>

int main()
{
    int a[5] = {1, 2, 3, 4, 5};
    int* p1 = (int*)(&a + 1);  //p[-1] = *(p - 1);
    int* p2 = (int*)((int)a + 1);
    int* p3 = (int*)(a + 1);

    printf("%d, %d, %d\n", p1[-1], p2[0], p3[1]);   //5, 33554432, 3

    return 0;
}
// A. 数组下标不能是负数,程序无法运行
// B. p1[-1]将输出随机数,p2[0]输出2, p3[1]输出3
// C. p1[-1]将输出乱码, p2[0]和p3[1]输出2

6 数组参数

  • 数组参数作为函数参数时,编译器将其编译成对应的指针。
    • void f(int a[]); <-> void f(int *a);
    • void f(int a[5]); <-> void f(int *a);

结论:一般情况下,当定义的函数中有数组参数时,需要定义另一个参数来标识数组的大小。

实例分析:虚幻的数组参数

#include <stdio.h>

void func1(char a[5])
{
    printf("In func1: sizeof(a) = %d\n", sizeof(a));

    *a = 'a';

    a = NULL;
}

void func2(char b[])
{
    printf("In func2: sizeof(b) = %d\n", sizeof(b));

    *b = 'b';

    b = NULL;
}

int main()
{
    char array[10] = {0};

    func1(array);

    printf("array[0] = %c\n", array[0]);

    func2(array);

    printf("array[0] = %c\n", array[0]);

    return 0;
}

小结

  • 数组名和指针仅使用方式相同:数组名的本质不是指针、指针的本质不是数组。
  • 数组名并不是数组的指针,而是数组首元素的地址。
  • 函数的数组参数退化为指针。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值