C之指针与数组组合

我们在前面讲到数组的本质是一段连续的内存空间,那么它的大小为 sizeof(array_type) * array_size,同时数组名可看做指向数组第一个元素的常量指针。那么问题来了,数组 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;
}

编译结果如下

我们看到数组 a 相当于一个常量指针,而它便指向的首元素的地址,a + 1 便是首元素的地址加 4,也就是数组第二个元素的地址。因为指针 p int 型,所以  p + 1 相当于加 4。

指针是一种特殊的变量,它与整数的运算规则为 p + n <==> (unsigned int)p + n*sizeof(*p);那么便是当指针指向同一类型的数组的元素时:p + 1 将指向当前元素的下一个元素;p - 1 将指向当前元素的上一个元素。指针之间只支持减法运算,并且参与减法运算的指针类型必须相同。p1 - p2 <==> ((unsigned int)p1 - (unsigned int)p2)/sizeof(type);注意:a> 只有当两个指针指向同一个数组中的元素时,指针相减才有意义,其意义为指针所指元素的下标差;b> 当两个指针指向的元素不在同一个数组中时,结果为定义。

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

下来我们来看个示例代码,代码如下

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

我们在第3行定义的宏是求这个数组元素的个数,在第9行定义的指针 pEnd 为数组首元素的地址加上数组元素个数,那么它刚好指向数组最后一个元素的临界。这是 C 语言中的灰色地带,在 C 语言中是合法的。我们来看看编译结果

我们看到结果是如我们所想的那,因为是 char 类型的数组,所以 pEnd = pBegin + 5。

数组名可以当做常量指针使用,那么指针是否也可以当做数组名来使用呢?我们往后接着说,在数组中的访问方式有两种:1、以下标的形式访问数组中的元素;2、以指针的形式访问数组中的元素。那么这两种方式有何区别呢?当指针以固定增量在数组中移动时,效率要高于下标形式。尤其是指针增量为 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;
}

我们看到在程序第6行定义指针 p 并将它指向数组 a,接下来就是我们之前说的到底指针是否也可以当做数组名来使用呢,如果可以第11行便不会报错。在第16行我们以指针的形式来打印数组 a 中的值,第23行则验证我们上面 i[a] 这种写法是否正确,第28行则通过下标形式来访问数组。我么来看看编译结果

我们看到程序没报错并且完美执行,这就回答了我们上面的问题和疑问。但是得注意:在现代编译器中,生成代码优化率已大大提高,在固定增量时,下标形式的效率已经和指针形式相当了;但从代码的可读性和维护的角度来看,下标形式更优秀,这就是为什么我们平时见到的代码中的数组都是以下标形式访问的啦。

我们下来再做个实验,看看数组和指针的区别

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

ext.c 代码

int a[] = {1, 2, 3, 4, 5};

我们看到在 ext.c 中定义了一个数组,我们先以数组的方式在 test.c 中访问,看看打印结果

我们看到的结果和我们想的是一致的,&a 就代表数组的地址,a 就代表数组首元素的地址,两个是相同的。*a 的值便是数组中第一个元素的值啦。我们再来将 test.c 中的第5行改成 extern int* a; 这样呢,我们来看看编译结果

我们看到发生段错误了,这是什么情况呢?数组 a 的值为 1,*a 发生段错误了,在内存中,数组的值是这样存储的 0001 0002 ... 0005(大端机器)。那么 a 自然也就为 1了,计算机中的 1 地址处为内核态,用户态的程序想要访问内核态的地址,计算机当然会报错。

那么 a 和 &a 有何区别呢? a 为数组首元素的地址,&a 为整个数组的地址。a 和 &a 的区别在于指针运算。a + 1 ==> (unsigned int)a + sizeof(*a);&a + 1 ==> (unsigned int)(&a) + sizeof(*&a) ==> (unsigned int)(&a) + sizeof(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;
}

我们看下编译结果

我们来分析下,第一个 p1[-1] ==> p1[&a +1 - 1] =>p1[&a],自然它的值也就为 5 了。p3[1] ==> (a + 1 +2) ==> (a + 2),自然也就为 3 啦。第二个的数感觉是随机数,但我们仔细分析下,它是首地址加 1,也就是往后移一位。这个数组在小端系统中,就是 1000 2000 ... 5000这样分布的,后移一位就变成了 0002,便是 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;
}

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

我们在 func1 中打印它的参数大小,并且以指针方式进行赋值和指向 NULL,如果是数组的话便会报错。我们来看看编译结果

我们发现两函数的数组参数都被当成指针来处理了。 通过本节对指针和数组的学习,总结如下:1、数组声明时编译器自动分配一片连续的内存空间,指针声明时只分配了用于容纳地址值的4字节空间;2、指针和整数可以进行运算,其结果为指针。指针之间只支持减法运算,其结果为数组元素下标差;3、指针之间支持比较运算,其类型必须相同;4、数组名和指针仅使用方式相同,数组名的本质不是指针,指针的本质不是数组;5、数组名并不是数组的地址,而是数组首元素的地址;6、函数的数组参数化为指针。

欢迎大家一起来学习 C 语言,可以加我QQ:243343083。

原文地址:http://blog.51cto.com/12810168/2105582

时间: 2024-10-09 14:02:45

C之指针与数组组合的相关文章

娓娓道来c指针 (3)指针和数组

(3)指针和数组 在c中指针和数组似乎有着千丝万缕的关系.其实它们不是一回事:指针是指针,数组是数组,两者不相同. 说它们有关系,不过是因为常见这样的代码: int main() { int array[] = {1,2,3,4,5}; int n = sizeof(array) / sizeof(int); int *p = array; int i; for (i = 0; i < n; i++) printf("p[%d]...%d\n", i, p[i]); system

(三)指针和数组

在c中指针和数组似乎有着千丝万缕的关系.其实它们不是一回事:指针是指针,数组是数组,两者不相同. 说它们有关系,不过是因为常见这样的代码: int main() { int array[]= {1,2,3,4,5}; int n=sizeof(array)/sizeof(int); int *p=array; int i; for(int i=0; i<n; i++) { printf("p[%d]...%d\n",i,p[i]); printf("*(p+%d)...

指针和数组

指针的几个要点: 一 指针的定义,赋值 要区分: int a; int *p = &a; 这是定义的时候初始化,即赋值 ======================= int a; int *p; p = &a; 定义,初始化分开,  就这里我迷惑了好久 ========================== 多个(修饰符)修饰变量,看变量名先和谁结合(先看后缀)(ps:符号和变量之间的空格不要计算,和距离无关 int*和int *....... int * p [5]    先和[ ]结合

C---通过指针访问数组

C语言规定:如果指针变量P已指向数组中的一个元素,则P+1指向同一数组中的下一个元素. 引入指针变量后,就可以用俩种方法来访问数组元素了. 如果p的初值为&a[0],则: P+i 和a+i 就是a[i]的地址,或者说它们指向a数组的第i个元素. *(p+i)或*(a+i)就是p+i或a+i所指向的数组元素,即a[i].例如,*(p+5)或*(a+5)就是a[5]. 指向数组的指针变量也可以带下标,如p[i]与*(p+i)等价. 根据以上叙述,引用一个数组元素可以用: 1.下标法:即用a[i]形式

指针、数组的理解与关系

一.指针的本质:变量,指针变量就是指针变量int *p:两个变量,一个p(指针变量本身)是int *类型的 另一个是*p(指针指向的那个变量)是int类型的注:指针说白了就是指针类型,前面定义的int类型是为了说明指针指向的那个数的类型,所以指针的解析方式都是按地址来解析的(不管你是char *还是double *,解析方式都是地址)而指向的那个数的类型就要看你怎么定义的了例如:int *aa是按照地址来解析的:*a则是按照int类型来解析的. (1)所有的类型的数据存储在内存中,都是按照二进制

程序设计基石与实践之C语言指针和数组基础

英文出处:Dennis Kubes:  <Basics of Pointers and Arrays in C>. 关于C语言中指针和数组的争论就像是一场恶战.一方面,有些人觉得,所有人都必须承认指针与数组是不同的.而另一些人则认为数组被当成指针来处理,因此它们不应该有什么区别.这种现象让人迷惑.然而,这两种说法其实都是正确的. 数组不是指针,指针也不能说是数组.在C语言中,指针仅在内存中代表一个地址,而数组是许多连续的内存块,多个类型相似的元素存储在其中.更深入的解释,请参考我之前的博文&l

C语言--&gt;(十一)指针于数组

知识点: • 指针与变量 (指向变量的指针)• 指针与函数 (地址传递) • 指针与数组 (指向数组的指针) • 指针与字符串 =================================数组的指针 1.什么是数组指针 1)数组的指针是指数组在内存的的起始位置 2)数组的第一个元素和数组的起始地址一致 2.数组名的本质 1)数组名本质上是一个地址常量,代表的是数组的首地址也就是第一个元素的地址 数组名表示表示数组的起始地址,不表示整个数组,不能对数组整体赋值. 3.数组名为一个指针常量,可

指针的数组

前言:上一篇,提到了栈和堆,想必你也清楚了栈和堆的区别,现在 通过指针的数组来看一下,指针是如何操作栈和堆的. 栈里面的数组: <span style="font-size:18px;"> int a[10]; //这个空间实在栈内存中申请的 int *p =NULL: p =a; //a是数组的首地住所以可以直接赋值给指针p p[0]=10; //就等同于a[0]=10;</span> 这个是堆里面的数组: <span style="font-

转: 浅谈C/C++中的指针和数组(二)

转自:http://www.cnblogs.com/dolphin0520/archive/2011/11/09/2242419.html 浅谈C/C++中的指针和数组(二) 前面已经讨论了指针和数组的一些区别,然而在某些情况下,指针和数组是等同的,下面讨论一下什么时候指针和数组是相同的. C语言标准对此作了说明: 规则1:表达式中的数组名被编译器当做一个指向该数组第一个元素的指针: 注:下面几种情况例外 1)数组名作为sizeof的操作数 2)使用&取数组的地址 规则2:下标总是与指针的偏移量