C语言进阶:第29课:指针和数组分析(下)

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

数组的访问方式:
下标的形式访问数组中的元素;
指针的形式访问数组中的元素;

指针以固定增量在数组中移动时,效率高于下标形式,指针增量为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;
}

注意:

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

但从可读性和代码维护的角度来看,下标的形式更优。

数组和指针的不同之处:

在ext.c中:

int a[] = {1, 2, 3, 4, 5};             //定义数组

在test.c中:

#include <stdio.h>

int main()
{
    extern int* a;                     //声明指针
    
    printf("&a = %p\n", &a);           //a定义时的地址值
    printf("a = %p\n", a);             //在这个地址值上取第一个数作为地址
    printf("*a = %d\n", *a);           //ox1,这个内存地址是预留给操作系统的,所以无法访问
    
    return 0;
}

编译为报错,运行:

./a.out
&a = 0x804a014
a = 0x1
段错误

但如果这样声明的时候:

#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;
}
即外部声明和定义是一致的。编译运行的结果:
&a = 0x804a014
a = 0x804a014
*a = 1

为什么会出现这种情况呢?数组名是不是指针呢?

首先必须明确,数组名并不是指针。

在外部.c文件中,定义了一个包含五个int类型的数组,所以编译器会在内存中分配20个字节的空间,其地址如上所示。换句话说,标识符a在编译后得到一个新的意义,代表了一个地址:0x804a014,接下来,当编译text.c时,发现声明了的extern int* a,编译器在此时将默认为a为一个外部定义的指针,代表一个地址。

所以第一行打印就打印了a的地址值,第二行打印呢?a的值是什么呢?a是一个指针变量,变量有四个字节,所以编译器会在a的地址处读取四个字节的地址,因为此时a被认为是一个指针,所以此时读取的是在这个地址(0x804a014)上的0001这个数,即0x1。但是0x1这个地址是预留给操作系统的,但凡用户态的程序要访问这个地址(第三行打印),就会出现段错误。

a与&a的区别:

        a为数组首元素的地址

        &a为整个数组的地址

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

#include <stdio.h>

int main()
{
    int a[5] = {1, 2, 3, 4, 5};
    int* p1 = (int*)(&a + 1); 
    int* p2 = (int*)((int)a + 1);
    int* p3 = (int*)(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
值得注意的是p2,在对a进行运算时进行了强制类型转换,这样进行运算时,就不是地址值了。

将代码修改如下,更方便理解:

#include <stdio.h>

int main()
{
    int a[5] = {1, 2, 3, 4, 5};
    int* p1 = (int*)(&a + 1); 
    int* p2 = (int*)((int)a + 1);
    
    printf("a = %p\n", a);
    printf("*(int*)((int)a) = %d\n", *(int*)((int)a));
    printf("*(int*)((int)a + 1) = %d\n", *(int*)((int)a + 1));
    
    int* p3 = (int*)(a + 1);
    
    printf("%d, %d, %d\n", p1[-1], p2[0], p3[1]);
    
    return 0;
}

编译运行:

~/will$ ./a.out
a = 0xbffe3780
*(int*)((int)a) = 1
*(int*)((int)a + 1) = 33554432
5, 33554432, 3

p2 = 0xbffe3780 + 1 =  0xbffe3781;     p2已经不是指针运算了,即在原地址处加1,

p2[0] = *p2 ; 即在0xbffe3781处的数据:

0002
1000 2000 3000 4000 5000//小端系统

*p2 ---> 0x0200 0000

0x02000000十六进制转化为十进制为33554432

数组参数:

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;  //此处的a是指针,不表示数组
}

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

编译运行:

~/will$ gcc test.c
~/will$ ./a.out
In func1: sizeof(a) = 4
array[0] = a
In func2: sizeof(b) = 4
array[0] = b

数组参数并不是数组了,而是一个指针。

小结:

    数组名和指针仅使用方式相同

        数组名的本质不是指针

        指针的本质不是数组

数组名并不是数组的地址,而是数组元素的地址(体现在指针运算)

函数的数组参数退化为指针。

猜你喜欢

转载自blog.csdn.net/qq_28388835/article/details/80390337