首页
学习
活动
专区
圈层
工具
发布
社区首页 >专栏 >C/CPP数组的深入理解 | 指针与数组 | 一二三维数组

C/CPP数组的深入理解 | 指针与数组 | 一二三维数组

作者头像
CtrlX
发布2023-03-21 11:39:17
发布2023-03-21 11:39:17
1.6K0
举报
文章被收录于专栏:C++核心编程C++核心编程

引入

整型数组和字符型数组

数组是一段连续的内存,除了定义数组变量,也会用指针来表示数组,但是数组和指针不是完全相同的,数组有很多特性,例如数组有确定数量的元素,而指针只是一个标量值。

编译器用数组名来记住这些属性,只有当数组名在表达式中使用时,编译器才会认为它产生一个指针常量(不是指针变量,数组名的值是指针常量,详见C和指针P142)。

一维二维三维数组

我们习惯的认为的1D,2D,3D数组的样子:

但是计算机中数组的实际样子:

这三个函数分别是给一维二维三维数组赋值,但是他们的汇编指令显然完全相同,因此无论数组是一维的还是高维的,他们都是一段连续的一维内存。(注意理解数组存储的原理很关键)

以二维数组详细分析

二维数组的行地址、列地址,与元素的存储

1、连续存储

2、二维数组的行地址与列地址

1)行地址 1、二维数组中,数组名a的值,是数组a首元素a[0][0]的地址,即&a[0][0],第一行第一个元素的地址;

2、二维数组中,数组名a+1是数组a的元素a[1][0]的地址,即&a[1][0],第二行第一个元素的地址;

2)列地址 1、二维数组中,a[0]的值,即该数组的首元素a[0][0]的地址,即&a[0][0]

2、二维数组中,a[0]+1的值,是数组元素a[0][1]的值,即&a[0][1]

3)混合一下 1、二维数组中,“a[0]+1”是指向数组元素a[0][1]的地址,“a[1]+2”是指向数组元素a[1][2]的地址;

2、同样的,二维数组中,“*(a+1)+2”是指向数组元素a[1][2]的地址,与“a[1]+2”相等;

PS:*(a+1)表示第2行的行地址;*a+1表示第一行第二个元素的地址;

3、二维数组中,*(*(a+1)+2))是数组元素a[1][2]的值!!!

代码语言:javascript
复制
#include <stdio.h>
int main()
{
    int a[2][3]={{1,2,3},{4,5,6}};
    int i,j;
    int *p1,*p2,*p3,*p4,*p5,*p6;
    printf("array a is :\n");
    for(i=0;i<2;i++)
    {
        for(j=0;j<3;j++)
        {
            printf("%d ",a[i][j]);
        }
        printf("\n");
    }
    printf("array b is :\n");
    p1=a;//二维数组的数组名,即首元素a[0][0]的首地址;
    p2=a+1;//a+1是数组a的元素a[1][0]的地址,即&a[1][0]
    p3=a[0];//同a[0][0]的地址,即第一行第一个元素的地址
    p4=a[1];//同a[1][0]的地址,即第二行第一个元素的地址
    printf("p1=a指向的值是:%d;地址是:%d\n",*p1,p1);
    printf("p2=a+1指向的值是:%d;地址是:%d\n",*p2,p2);
    printf("p3=a[0]指向的值是:%d;地址是:%d\n",*p3,p3);
    printf("p4=a[1]指向的值是:%d;地址是:%d\n",*p4,p4);
    p5=a[0]+1;//指向第一行,第二列元素的地址;
    p6=a[1]+1;//指向第二行,第一列元素的地址;
    printf("p5=a[0]+1指向的值是:%d;地址是:%d\n",*p5,p5);
    printf("p6=a[1]+1指向的值是:%d;地址是:%d\n",*p6,p6);
    printf("*(a+1)指向的地址是:%d\n",*(a+1));
    printf("*(*(a+1))指向的值是:%d\n",*(*(a+1)));
    printf("*(a+1)+2指向的地址是:%d\n",(*(a+1)+2));
    printf("*(a+1)+2指向的值是:%d\n",*(*(a+1)+2));
    printf("*a+2指向的值是:%d;地址是:%d\n",*(*a+2),*a+2);
    return 0;
}

3.行地址,列地址的等价写法

注:在二维数组a[i][j]中,a[i]是“行名”,等价于指针;

a[0]等价于a,都表示指针;

a[1]等价于a+1,都表示指针;

a[1]+1等价于*(a+1)+1,都表示指针

注意防止越界!

数组与指针

数组与指针的关系

因此函数以数组作为参数时传入的都是数组的首地址,将首地址存放在一个临时的指针变量里。

代码语言:javascript
复制
//例如
void func(int *arr){}

作为函数参数时的规范:通常为了防止数组越界还会增加一个参数来表示数组长度。越界的危害见堆栈隐患文章(数组越界可能会导致函数的地址被篡改)

代码语言:javascript
复制
//规范写法
void func(int *arr,int length){}

数组的[]本质上是偏移量

数组定义中的中括号就是表示它是个数组,属于语法范畴(就跟函数调用里面的逗号,语句后面的分号,还有语句块的大括号一样),不算运算符,不能更改和重载,例如 int a[10]或者int *a = new int[10]等。

但是C和C++语言里中括号还有个用法就是作为一个运算符,是一个叫做“数组下标运算符”的双目运算符,即a[b]。其默认等价于*(a+b)。但是在你自己定义的类中,这个运算符是可以重载的,使之不等于*(a+b),这个时候这个运算符的意义就不是“数组下标”了,而是你自己定义的了,甚至可以像括号那样用。

代码语言:javascript
复制
int a[10];
a[1] == *(a + 1)//根据指针我们知道[1]其实就是a地址向后偏移了一个int,即指针的+操作是地址的偏移,指向下一个元素的地址。

所以理解数组[]的原理尤为关键。

代码语言:javascript
复制
int a[10] = {1,2,3,4,5};
b = a[1];//b = 2  ,b = *(a + 1)
b = b[-1];//b = 1  ,b = *(a +1 -1)

指针定义与数组定义字符串的区别(关键)

字符串的两种定义方式

代码语言:javascript
复制
#include<stdio.h>
int main()
{
    char *char1 = "helloworld";//指针的方式定义,注意这种定义方式在vs中报错
    char char2[11] = "helloworld";//数组的方式定义
    return 0;
}

PS:易错点,helloworld这十个字符能否放到char char2[10]中?显然不能,勿忘字符串的结尾符号\0也占一个位置。

区别

1.字符串的构造方式不同

char *char1 = "helloworld"是用char1这个指针指向这个字符串。这个helloworld存放在字符串常量区

char char2[11] = "helloworld";是在内存里创建了一块空间,把常量区的helloworld拷贝并存放到这块内存中。

2.字符串单个元素的修改情况不同

代码语言:javascript
复制
#include<stdio.h>
int main()
{
    char *char1 = "helloworld";//指针的方式定义
    char char2[11] = "helloworld";//数组的方式定义
    
    //修改元素
    *char1[0] = 'h';//报错,字符串常量不可以修改!
    char2[0] = 'h';//正常运行
    
    return 0;
}

要操作字符串,只能把它用数组方式,引入存放变量的区,才能操作,在常量区不能对字符串常量进行修改操作。

代码语言:javascript
复制
#include<stdio.h>
int main()
{
    
    char char2[11] = "helloworld";//数组的方式定义
    char *char1 = char2;//指针的方式定义
    
    //修改元素
    *char1[0] = 'h';//可以修改,正常运行
    char2[0] = 'h';//正常运行
    
    return 0;
}

3.字符串总体修改情况不同

代码语言:javascript
复制
#include<stdio.h>
int main()
{
    char *char1 = "helloworld";//指针的方式定义
    char char2[11] = "helloworld";//数组的方式定义
    
    //修改元素
    *char1 = 'abcde';//正常运行
    char2 = 'abcde';//报错
    
    return 0;
}

用指针定义的字符串可以整体进行修改,因为其只是将指向的常量地址进行更改,指向了另一个字符串,常量区出现在程序编译前。

C/CPP标准都规定了:只有标量和结构体,才能出现在赋值操作符=的左侧,但是数组类型并不是一个标量,因此不能对数组执行赋值操作。(但是可以从键盘读入,见下面4)

4.能否用I/O函数从键盘输入字符串

代码语言:javascript
复制
#include<stdio.h>
int main()
{
    char *char1 = "helloworld";//指针的方式定义
    char char2[11] = "helloworld";//数组的方式定义
    
    char1 = char2;
    gets(char2);//合法,即使已经被初始化了,但是仍然能读取键盘输入重新赋值。
    puts(char1);
    puts(char2);
	
    /*
    Input:abcde
    Output:
          abcde
          abcde
    */
    return 0;
}

指针只能指向已经有了的,定义了的,所以用指针来定义的字符串不能从键盘读入值。

用数组来定义 的可以从键盘读入(哪怕已经被初始化为一个其他的值,仍能再次通过键盘进行赋值)。

5.处理效率不同(详见C Primer Plus)

用指针直接指向字符串常量时可以节省了普通定义数组时在内存创建空间的时间和空间。

6.作为函数参数,形式不同,但是传递的都是地址(详见C Primer Plus)

函数参数char *achar a[]相同,传递的都是地址。

本文参与 腾讯云自媒体同步曝光计划,分享自作者个人站点/博客。
原始发表:2022-10-01,如有侵权请联系 cloudcommunity@tencent.com 删除
目录
  • 引入
    • 整型数组和字符型数组
    • 一维二维三维数组
    • 二维数组的行地址、列地址,与元素的存储
      • 1、连续存储
      • 2、二维数组的行地址与列地址
      • 3.行地址,列地址的等价写法
  • 数组与指针
    • 数组与指针的关系
    • 数组的[]本质上是偏移量
  • 指针定义与数组定义字符串的区别(关键)
    • 字符串的两种定义方式
    • 区别
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档