29-指针和数组分析(下)

注:博客中内容主要来自《狄泰软件学院》,博客仅当私人笔记使用。

测试环境:Ubuntu 10.10

GCC版本:4.4.5

一、问题:

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

二、数组的访问方式

1) 以下标的形式访问数组中的元素   

int main()
{
    int a[5] = {0};
    
    a[1] = 3;
    a[2] = 5;
    
    return 0;
}

2) 以指针的形式访问数组中的元素

int main()
{
    int a[5] = {0};
    
    *(a + 1) = 3;
    *(a + 2) = 5;
    
    return 0;
}

三、下标形式VS指针形式

1) 指针以固定增量在数组中移动时,效率高于下标形式

2) 指针增量为1且硬件具有硬件增量模型时,效率更高

3) 下标形式与指针形式的转换

                   a[n]<-->*(a+n)<-->*(n+a)<-->n[a]

注意:

         现代编译器的生成代码优化率已大大提高,在固定增量时,下标形式的效率已经和指针形式相当;但从可

读性和代码维护角度来看,下标形式更优。(基本上都用下标形式)

 

实例分析
数组的访问方式
29-1.c
 
#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;      //==>a[i] = i + 10    重点是这里i[a]
    }
   
    for(i=0; i<5; i++)
    {
       printf("p[%d] = %d\n", i, p[i]);
    }
   
    return 0;
}
 

操作:

1) gcc 29-1.c -o 29-1.out编译正确,运行结果:

a[0] = 1
a[1] = 2
a[2] = 3
a[3] = 4
a[4] = 5
 
a[0] = 10
a[1] = 11
a[2] = 12
a[3] = 13
a[4] = 14
编程实验
数组和指针不同
29-2.c

#include <stdio.h>
 
int main()
{
    extern int* a;            //将数组声明为指针类型,数组元素都变为指针类型!!!
    //extern int a[];        //常规用法正确   
 
    printf("&a = %p\n", &a);          //打印指针a的本身的地址
    printf("a = %p\n", a);            //打印指针a指向的地址
    printf("*a = %d\n", *a);          //打印指针a所指向内存地址中的数据
   
    return 0;
}

ext.c
int a[] = {1, 2, 3, 4, 5};      //编译阶段就分配好内存地址,在上个函数中调用改变了地址

操作:

1) gcc 29-2.c ext.c -o 29-2.out编译正确,运行出现段错误:

&a = 0x804a014      
a = 0x1   //a[]被声明为int* 指针后,元素都变为指针类型,因此将元素中数值变为地址
段错误     //去0x1内存地址取数据,但是操作系统不允许去这个内存地址中访问数据

2) 修改代码:extern int a[]

#include <stdio.h>
 
int main()
{
    extern int a[];        //常规用法正确   
 
    printf("&a = %p\n", &a);          //打印指针a的本身的地址
    printf("a = %p\n", a);            //打印指针a指向的地址
    printf("*a = %d\n", *a);          //打印指针a所指向内存地址中的数据
   
    return 0;
}

gcc 29-2.c ext.c -o 29-2.out编译正确,打印结果:

&a = 0x804a020
a = 0x804a020
*a = 1

四、a和&a的区别

1) a为数组首元素的地址

2) &a为整个数组的地址

3) a和&a的区别在于指针运算

a+1   ==> (unsigned int)a + sizeof(*a)  //数组元素地址+1,元素下标移动+1
    
&a+1 ==> (unsigned int)(&a) + sizeof(*&a)    
相当于 ==> (unsigned int)(&a) + sizeof(a)    //增加整个数组长度

注:二者区别在指针运算时增加步长不一样

实例分析(笔试题)
指针运算经典问题
29-3.c
#include <stdio.h>
 
int main()
{
    int a[5] = {1, 2, 3, 4, 5};
    int* p1 = (int*)(&a + 1);     //指向数组末尾边界,擦边球
    //p[-1] ==>*(p1 - 1)==>a[4],指针回退1
    int* p2 = (int*)((int)a + 1); //数学运算,p2=数组元素首地址+1
    int* p3 = (int*)(a + 1);      //p3指向a[1]地址
   
    printf("%d, %d, %d\n", p1[-1], p2[0], p3[1]);
   
    return 0;
}
几种可能性
// A. 数组下标不能是负数,程序无法运行
// B. p1[-1]将输出随机数,p2[0]输出2, p3[1]输出3
// C. p1[-1]将输出乱码, p2[0]和p3[1]输出2

操作:

1)gcc 29-3.c -o 29-3.out编译正确,打印结果:

5, 33554432, 3

分析:

p1[-1] = 5: p1指向了数组末尾边界(擦边球)。p1[-1]相当于指针回退一个元素,即a[4],因此打印5。

p2[0] = 33554432: (int*)((int)a + 1)==>(int)a是数组地址转换成int型数值==>(int)a + 1==>
数组地址转换成int型数值后+1==>(int*)((int)a + 1)表示将相加后数值转换成int*,
此时指针指向的位置相当于数组名地址+1,即从首地址移动1个字节。
数组首地址相当于整体向后移动1个字节,因为linux是小端系统,转换成int*类型后,每个元素占用4字节,
4字节存储的数据为0x02000000=33554432
    
p3[1] = 3: (int*)(a + 1)==>a+1表示数组移动一个元素,即a[1]==>p3指向a[1]==>p3[1]等于a[2],即p3[1]=a[2]=3

注:linux是小端系统,低地址放低位数据。

五、数组参数

1) 数组作为函数参数时,编译器将其编译成对应的指针

                   void f(int a[]); <--> void f(int* a);

                   void f(int a[5]);<-->void f(int* a);

 

结论:

         一般情况下,当定义的函数中有数组参数时,需要定 义另一个参数来标示数组的大小,因为数组变为指针,参数无效。

实例分析
虚幻的数组参数
29-4.c
#include <stdio.h>
 
void func1(char a[5])  //数组变为指针
{
    printf("In func1: sizeof(a) = %d\n", sizeof(a));         //打印4
   
    *a = 'a';
   
    a = NULL;          //编译正确,说明这里a不是数组
}
 
void func2(char b[])
{
    printf("In func2: sizeof(b) = %d\n", sizeof(b));         //打印4
   
    *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;
}

操作:

1) gcc 29-4.c -o 29-4.out编译正确,打印结果:

In func1: sizeof(a) = 4
array[0] = a
In func2: sizeof(b) = 4
array[0] = b

分析:

        char a[]数组作为参数时,退化为指针!如果想完整获取数组信息,最好给用一个变量传递数组长度。
 

 

小结

1) 数组名和指针近使用方式相同

         - 数组名的本质不是指针

         - 指针的本质不是数组

2) 数组名并不是数组的地址,而是数组首元素的地址

3) 函数的数组参数退化为指针(重点)

发布了40 篇原创文章 · 获赞 1 · 访问量 1753

猜你喜欢

转载自blog.csdn.net/piaoguo60/article/details/104080190