8.2 指针和数组

一、指针和一维数组的关系

1. 数组名

int a[5];
// a 是数组名,5是数组元素的个数,从a[0]-a[4];
int b[3][4];
//3行4列,a[0][0]是第一个元素,a[i][j]是第i+1行,第j+1列的数
int c[5]={1};//其余元素都是0;
int d[8];//全是垃圾值
//所以只定义不初始化全是垃圾值,部分初始化,没被初始化的补0

一维数组名是一个指针常量,它存放的是一维数组第一个元素的地址

int a[3];
int b=5;
a=b;// error a是一个指针常量,不可以被改变
int main(void)
{
    int a[3]={1,2,3};
    printf("%#X\n",a);  // #X以十六进制整型格式输出
    printf("%#X\n",&a[0]);
    return 0;
}
//输出
0X61FE14
0X61FE14
两个值完全一样,说明一维数组名存放的就是一维数组第一个元素的地址

2. 确定一个一维数组需要几个参数?

【如果一个函数要处理一个一维数组,则需接收该数组的哪些信息?】

int f(int * pArr)
{
    *pArr= 0; 
    /*
    1. pArr代表的是数组名,也是数组第一个元素的地址
    2. *pArr 代表的就是数组的第一个元素: a[0]
    3. *(pArr+1)代表的就是数组的第二个元素a[1];
    4. 总结: a[i] = *(Parr+i) 
    */
    *(pArr+1) = 100;
}
int main (void)
{
    int a[3]={1,2,3};
    printf ("a[0]=%d,a[1]=%d\n",a[0],a[1]);
    f(a);
    printf ("a[0]=%d,a[1]=%d\n",a[0],a[1]);
    return 0 ;
}

为什么需要“数组名+数组长度”2个元素来确定一个一维数组?

  1. 由数组名知道该数组的起始位置(因为数组名存放的是数组第一个元素的地址)
  2. 但是由于,数组没有一个明显的结束的标志,所以需要长度这个元素来确定一个数组。(反之,字符串有'\0'这个结束标志)。
. . a[0] a[1] . . . 未知长度 .

3. 下标和指针的关系

//通过   数组名,长度  输出任意一个一维数组的全部元素
# include <stdio.h>
void f(int * pArr,int len)
{
    int i;
    for (i=0;i<len;++i)
    {
        printf("%d\n",*(pArr+i));
    }
    return;
}
int main (void)
{
    int a[3]={1,2,3};
    int * P;
    P=&a;
    *(a+1)=200;
    P[2]=100;
    f(a,3);
    return 0 ;
}
//输出
1
200
100

说明:a[i] = * (a+i) = * (P+i) = P[i]
实质上,数组名[下标]要以指针/地址的角度去理解,不要停留在表面

4. 指针变量的运算

  1. 两个指针变量不能相加,不能相乘除;只能想减。
  2. 如果,两个指针变量指向的是同一块连续空间的不同存储单元,则这两个指针变量想减才可能有意义。
一、显然这种没有实际意义
int main (void)
{
    int a=3;
    int b= 5;
    int *p = &a;
    int *q = &b;
    int x=p-q;
    return 0;
}
二、指向连续存储空间的不同存储单元时
int main (void)
{
    int a[5]={1,2,3,4,5};
    int *p = &a[1];
    int *q = &a[4];
    printf("p、q所指向的空间单元相隔的个数是: %d\n",q-p);
    return 0;
}
//输出:
p、q所指向的空间单元相隔的个数是: 3  

4. 一个指针变量所占字节

假设P指向char类型的变量 假设q指向 int 类型的变量 假设r指向 double 类型的变量

p、q、r 本身所占字节数是否一样?

sizeof(数据类型)
sizeof(变量名)

int main (void)
{
    int a = 4;
    int *p = &a;
    double b = 88.8;
    double *q = &b;
    float c = 4.19f;
    float *r = &c;
    char d = 'A';
    char * m = &d;
    printf("int *;double*;float*;char*分别所占的字节是:%d;%d;%d;%d",sizeof(p),sizeof(q),sizeof(r),sizeof(m));
    return 0;
}
//输出:
int *;double*;float*;char*分别所占的字节是:8;8;8;8

可见,所有类型的指针变量所占的字节数是相同的。 注意: VC++ 6.0 是4个字节
和不同的编译器以及系统有关。

为什么不管什么类型的指针变量所占字节数都是相同的?

  1. 因为指针变量保存的是所指向变量的首地址,言外之意就是无论指针指向什么类型的数据,它只保存一个首地址。
  2. 指针是靠指针变量类型来区分它要指向几个内存单元的。 虽然只保存首地址,但是根据指针变量类型却可以判断,这一个指针指向的是几个内存单元。
  3. 为什么指针只存首地址,却要用四个字节来存放?——说明这个地址很大,地址是内存单元的编号,给内存分格子,每个格子编上号,内存越大,格子分的越多,所需要的编号也就越大。 4G内存 也就是
    $ 2{32}=2{30} \times 2^4 $ (单位:字节)实际上和系统寻址能力有关,参考CSDN iOS_Asia 一个指针占几个字节?原理是什么呢?
  4. 内存地址是以字节为最小单位的。原因和其物理存储结构有关。此处参考:简书ID yanfeizhang——为什么内存地址以字节为单位?
  5. 既然4G内存也就是232字节,一个字节是一个地址,4G内存就需要 232 个编号,注意这是十进制,转换成2进制就是需要32位二进制,已知8位二进制是一个字节,所以32位二进制需要4个字节,所以一个地址在32位系统,4G内存,需要4个字节。

二、指针和二维数组的关系

1. 二维数组元素的地址

前面已经分析过,没有真正的多维数组,所谓多维数组也不过是多个一维数组
以下提到的数组皆以下例为准 int a [3][4]
该数组是含有3个元素的一维数组
只不过每个元素都相当于一个包含4个元素的一维数组。

a[0] 1 3 5 7
a[1] 9 11 13 15
a[2] 17 19 21 23

a[0]、a[1]、a[2] 分别是3个一维数组的数组名
a[0]这个一维数组,有4个元素,a[1]这个一维数组也有4个元素。

(一) 地址角度

  1. a[0]、a[1]、a[2] 分别是3个一维数组的数组名,a、a+1、a+2代表3个一维数组起始地址, a、a+1、a+2 就分别指向a[0]、a[1]、a[2]这3个一维数组。

  2. 一维数组名是一维数组第一个元素的地址。假设a[0]数组的第一个元素的地址是2000H也就是,那么a[1]、a[2]数组第一个元素的地址是多少?
    如图-例子数组 a[1] 是第二个一维数组第一个元素的首地址,值为 2016H

  3. 在一个一维数组中,数组名是第一个元素的地址,加 1就是第二个元素的地址,所以 a[0]+1 就表示第一行第二列元素的地址

  4. 数组某元素的地址还可以用 &元素 的方式表示。 例如第1行第2列的元素的地址是? &a[0][1]

  5. 已知在一维数组中,a[i]=*(a+i) , 又根据第3条, 所以在二维数组中, a[i][m] = *(a+i)+m, 所以二维数组第一行第二列的元素地址是: * (a)+1

  6. 综上:&a[m][n]=a[m]+n=*(a+m)+n 都表示的是第M行第N列元素的地址。

a---这个a可以是定义的二维数组的数组名,也可以是第一行的一维数组的数组名,也可以是第一行一维数组元素的首地址。

(二) 元素角度

  1. a[0]数组中的第 i 个元素怎么表示? 类比一维数组表达,应该是a[0][ i-1 ];根据前面指针和一维数组的联系。 提示一维数组里:a[i] = * (a+i) = * (P+i) = P[i]
    另一种表示a[0]数组中第i个元素的表达方式是:* (a[0]+i)
  2. 从地址角度想,*(地址)也代表该数组元素。
  3. 根据(一)从地址角度的总结
    综上:&a[m][n]=a[m]+n=*(a+m)+n 都表示的是第M行第N列元素的地址
    可以得出,第M+1行N+1列数组元素可以表示为:
    a[m][n]= * (*(a+m)+n) = *(a[m]+n)

2. 指向二维数组的指针变量

  1. 第一种思路:定义一个指针变量,依次指向二维数组的每一个元素。(因为二维数组本身也是线性存储的)
int main (void)
{
    int a[3][4]={1,3,5,7,9,11,13,15,17,19,21,23};
    int *p;
    for(p=a[0];p<a[0]+12;p++) //使 p 依次指向下一个元素
    {
        if ((p-a[0])%4==0) //P 移动4次后换行
        {
            printf("\n");
        }
        printf("%4d",*p);
    }
    printf("\n");
    return 0;
}
  1. 第二种思路:定义一个指针变量,指向一个一维数组(二维数组的一行),依次指向每一行(每一个一维数组)
//输出一个二维数组指定某一行某一列的值
int main (void)
{
    int a[3][4]={1,3,5,7,9,11,13,15,17,19,21,23};
    int (*p)[4],i,j;//指针变量P指向包含4个整形元素的一维数组
    p=a; //P指向二维数组的第0 (1) 行
    printf("Please enter row and colum:");
    scanf("%d,%d",&i,&j);//输入要输出的是某行某列
    printf("a[%d,%d]=%d\n",i,j,*(*(p+i)+j));
    return 0;
}

3. 用二维数组指针变量作为函数参数

# include <stdio.h>
/*
1. 求3个学生,每个学生有4门课的总成绩的平均值
2. 计算第n个学生的成绩
*/
void average(double *p,int n)  //n是数组元素个数
{
    //这个函数实际上把二维数组当作一维数组来处理的
    double *p_end; //创建一个指针变量指向二维数组的最后一个元素
    double sum = 0,aver;
    p_end = p+n-1; // 结尾指针指向数组的最后一个元素
    for (;p<p_end;p++)
    {
        sum = sum + *p; //通过指针依次指向二维数组的每一个元素算累加和
    }
    aver = sum/n;
    printf("average=%5.2lf\n",aver);
}
void search(double (*p)[4],int n) //p是指向具有4个元素的一维数组的指针
{
    int i;
    printf("The score of No.%d are:\n",n);
    for(i=0;i<4;i++) // i用来循环第n个学生的每一科成绩
    {
        printf("%.2lf  ",*(*(p+n)+i)); //输出第n行第i列元素
    }
    printf("\n");
}

int main(void)
{
    double score[3][4]={{66,67,89,99},{65,78,98,66},{79,69,95,100}};
    average(*score,12);// *score符合函数参数输入地址(指针)的要求,*score 是数组第一个元素的地址
    search(score,2);
    return 0;
}
/*输出
average=72.58
The score of No.2 are:
79.00  69.00  95.00  100.00
*/

本文章使用limfx的vsocde插件快速发布