首页 > 代码库 > C语言学习笔记(六) 指针和数组

C语言学习笔记(六) 指针和数组

使用C语言就必然会使用到指针和数组。看下面的代码:

int main(int argc, char** argv){
    int a[4] = {1,3,5,7};
    int *p = a;
    int i;
    for (i=0; i<4;i++){
        printf("i=%d, p[i]=%d, *(p+i)=%d, a[i]=%d, *(a+i)=%d\n",
                i, p[i], *(p+i), a[i], *(a+i));
    }
    return 0;
}

似乎二者的用法完全相同,但其实指针和数组没有任何关系。这里先总结下指针和数组的区别,后面再详细区分下两者作为参数的情形及其用途。


指针和数组的区别

指针数组
保存数据的地址,任何存入指针变量 p 的数据都会被当作地址来处理。p 本身的地址由编译器另外存储,存储在哪里,我们并不知道。
保存数据,数组名 a 代表的是数组首元素的首地址而不是数组的首地址。&a 才是整个数组的首地址。a 本身的地址由编译器另外存储,存储在哪里,我们并不知道。
间接访问数据,首先取得指针变量 p 的内容,把它作为地址,然后从这个地址提取数据或向这个地址写入数据。指针可以作为左值,也可以作为右值。
直接访问数据,数组名 a 是整个数组的名字,
数组内每个元素并没有名字。只能通过“具
名 +匿名”的方式来访问其某个元素,不能把
数组当一个整体来进行读写操作。即数组名不能作为左值。

访问的实质是先取 p 的内容然后加上i* sizeof(类型)个 byte 作为数据的真正地址。访问的实质都是 a 所代表的数组首元素的首地址加上 i*sizeof(类型 )个 byte 作为数据的真正地址。
通常用于动态数据结构
通常用于存储固定数目且数据类型相同的元素。
相关的函数为 malloc 和 free。
隐式分配和删除
通常指向匿名数据(当然也可指向具名数据)
自身即为数组名


一维数组参数

先看下面的示例代码:

void foo(int a[10]){
    printf("%d",a[0]);
}

int main(int argc, char** argv){
    int a[2] = {1,2};
    foo(a[10]);
    return 0;
}

上面的代码至少有两个问题:1)a[10]是肯定不存在的,但编译器并不实际计算地址,因此不能发现这样的错误。2)函数参数实际上需要一个int *指针,但我们传递的是一个int类型的数据。这时候把a[10]里面的数据当成指针地址访问,肯定也会出问题。此外,函数的声明很容易让读者误认为只能传递长度为10的数组。

这里有一个规则:C语言中,当一维数组作为函数参数的时候,编译器总是把它解析成一个指向其首元素首地址的指针。这么做主要是从不浪费大量时间去拷贝数组的角度考虑的。基于这一点,实际传递的数组大小与函数形参指定的数组大小并没有关系。


一级指针参数

要时刻注意:函数中使用的参数变量是实参的一份拷贝。看下面的代码:

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

void allocateMemory(char *p, int size){
    p = (char *)malloc(size * sizeof(char));
}

int main(int argc, char** argv){
    char *p = NULL;
    allocateMemory(p, 30);
    strcpy(p, "Hello");        //这里会出现错误
    free(p);                   //并没有效果
    return 0;
}

上面的代码在运行到strcpy时p的值仍然为NULL,我们试图分配的空间被赋给allocate函数里的拷贝变量去了,发生了内存泄漏和空指针。而这个拷贝变量我们根本没法处理,它是编译器自动分配回收的。所以使用一级指针参数一定要注意这个问题。要继续使用一级指针并解决这个问题,可以给函数加上返回值:

char * allocateMemory(char *p, int size){
    p = (char *)malloc(size * sizeof(char));
    return p;
}


多级指针参数

还有一种办法是使用多级指针:

void allocateMemory(char **p, int size){
    *p = (char *)malloc(size * sizeof(char));
}

int main(int argc, char** argv){
    char *p = NULL;
    allocateMemory(&p, 30);
    strcpy(p, "Hello");
    free(p);
    return 0;
}

注意,这里使用了二级指针,在使用的时候参数要用&p, 而不是p。这样传递过去的是p的地址,被调函数内部拷贝一个地址并不影响p值的使用,也就达到了我们的目的。


多维数组参数

数组有一维的,也有二维以上的。由于内存是线性的,我们把高维数组理解为数组的数组。但有一条规则却不能如此递归:C语言中,当一维数组作为函数参数的时候,编译器总是把它解析成一个指向其首元素首地址的指针也就是说只有一维数组才是如此,当数组超过一维时,将第一维改写为指向数组首元素首地址的指针之后,后面的维再也不可改写。比如: a[3][4][5]作为参数时可以被改写为(*p)[4][5]。在声明多维数组为参数的时候,也只能省略第一维的长度。例如下面的声明:

void funa(int a[1][3]){}    //正确
void funb(int a[][3]){}     //正确
void func(int a[][]){}      //错误

C语言中还有一个复杂的指针叫做函数指针,这将在下一篇笔记里记录。本文先到这里。

本文出自 “飞翔的猫咪” 博客,请务必保留此出处http://flyingcat2013.blog.51cto.com/7061638/1573280

C语言学习笔记(六) 指针和数组