• 【原创】浅谈指针(十三)指向数组的指针


    前言

    这两天又在首页看见指针的文章了,随手再来写一篇。本来想先写static的下集的,后来发现似乎写的有些问题,草稿已经在写了,预计后面不久再发布。
    指针其实是C++或是C语言中必不可少的一部分。即使说,很多情况下我们并不会直接使用到指针,但是指针的一些知识同样在其它的地方(哪怕看似和指针无关的地方)奏效。

    1.预备知识的复习

    1.1.数组

    数组在表达式中,其最高一维会被转化为指针。对于一维数组,其呈现的形态就是在表达式中转化为指针。函数的参数也是表达式。

    #include<iostream>
    using namespace std;
    void func(char s[]){
      cout<<sizeof(s)<<endl;
    }
    int main(){
      char s[]="hello";
      cout<<sizeof(s)<<endl;
      func(s);
    }
    

    在64位环境下的输出:

    6
    8
    

    1.2.VLA

    在C99中,对于非static修饰的局部变量,可以在定义中,数组的元素写成变量,这一功能叫做可变长数组(Variable Length Array,简称VLA)。
    下面是一段倒序输出数组的代码:

    #include<iostream>
    using namespace std;
    
    int main(){
        int n;
        cin>>n;
        int s1[n];
        for(int i=0;i<n;i++)cin>>s1[i];
        for(int i=n-1;i>=0;i--)cout<<s1[i]<<" ";
    }
    

    其中,"int s1[n]"一句就是运用到了可变长数组。
    但在C11中,VLA降为了可选功能,而且VLA只能使用在非static的局部变量,用途相比起来也比较有限。当然,很多情况下还是使用这一功能比较方便。

    2.指向数组的指针

    指针既然可以指向单个元素,因此也可以指向其他的内容,例如数组。

    2.1.指向二维数组

    二维数组的类型是int [][],放入表达式中,最高一维会被转化为指针,也就是int (*)[]。我们就可以使用这样类型的指针来指向这个二维数组。

    #include<iostream>
    using namespace std;
    
    int main(){
        int a[2][3]={1,2,3,4,5,6};
        for(int i=0;i<2;i++){
            for(int j=0;j<3;j++){
                cout<<a[i][j]<<" ";
            }
            cout<<endl;
        }
        
        int (*p)[3]=a;
        for(;p!=&a[2];p++){
            for(int j=0;j<3;j++){
                cout<<(*p)[j]<<" ";
            }
            cout<<endl;
        }
    }
    

    在代码的第13行中,声明了int (*p)[3],它表示“指向元素个数为3的数组的指针”。
    之前的文章中提到过网页链接,二维数组在内存中是连续排列的:

    如果使用普通的int*指针指向这个数组,每次前进的是sizeof(int)个字节:

    而使用“指向数组的指针”,每次前进的是这个数组的大小(即int[3]的大小,3*sizeof(int))
    如图所示,一次就前进了0x0c。

    2.2.注意事项

    指向数组的指针,类型表示:int (*a)[2];
    而在表达式中,二维数组int a[2][2];的最高一维转化为了指针,因此就变为了“指向数组的指针”。

    另外,“指向数组的指针”和“指向数组首个元素的指针”是截然不同的。在表达式中,数组的最高维会被转化为指针,此时的指针,指的是“指向数组初始元素的指针”。

    int (*array_p)[3];
    

    可以用来声明一个指向“长度为3的数组”的指针。

    在数组前,加上&取地址,返回的就是指向数组的指针:

    int array[3];
    int (*array_p)[3];
    
    array_p=&array;
    

    原本是int[3]类型,加上一层*,结果就是int (*)[3]
    对于scanf在输入字符串的时候,很多人还是这样写的:

    scanf("%s",&s);
    

    这样写看似没有问题(实际上,由于后面的s在可变长参数中,没有原型声明,也不会出问题),但是是错误的写法。因为s是数组,因此加上&后变为了“指向数组的指针”,而%s只需要传入一个指向char的指针。
    对于指向数组的指针,+1之后,真正加上的是它指向的数组的长度。(由于指针前进1,前进的是它所指向的值的大小)参考下图:

    2.3.数组与指针之间的转化

    • 规则:在表达式中,数组的最高一维会被转化为指针。
      • 特例1:对于sizeof(表达式)的形态,这是一个特例。(否则数组的长度是无法输出的)
      • 特例2:在初始化char数组的时候,编译器会自动把它解释为初始化的列表。
    char s[]="abc";
    

    本质上是:

    char s[]={'a','b','c','\0'};
    

    的简便写法。这种解读只有在初始化列表的时候可行。

    • 规则2:当数组解读为指针时,这个指针不可作为左值。
      • 左值在英语中称为"lvalue",但是l并不代表left,而是locator(表示位置的事物)的意思。本质上,左值就是指可以出现在表达式的左边,确切的说,就是有自己的内存空间,可以被赋值的东西、
      • 例如a=3中,a就是左值。而在a+1=3赋值语句中,由于a+1没有自己的内存空间,无法被赋值,因此它不是左值。

    例如:

    char s[10];
    s="abc";
    

    这段代码是错误的。

    2.4.应用

    在函数的参数中,二维数组会被解读为指向数组的指针。

    int f(int a[10][10]);
    

    与下面等价:

    int f(int a[][10]);
    int f(int (*a)[10]);
    

    但是注意,不能这么写:

    int f(int a[][]);
    

    因为指向数组的指针需要知道它的长度,不然在a++的时候,就不知道前进多少位置了。
    当第二维的数字是2的时候,每次的前进是这样的:

    而当第二维的数字为5,每次的前进是这样的:

    而最高一维可以省略,因为无论最高维是多少,都和前进的字节数无关。
    对于数组a[m][n],二维数组的公式是a[i][j]=*(*(a+i)+j)。本质上说,二维数组可以看做“数组的数组”。
    其中(a+i)每一次增加的长度也就是sizeof(a[i]),而sizeof(a[i])取决于它指向的内容(a可看做指向数组的指针,它的指向是j相关的一维),因此长度就是isizeof(int)n。
    (a+i)+j中,加上的j前进的长度是sizeof(int)。

    由此,我们可以看出,二维数组的寻址和j无关。

    完。

  • 相关阅读:
    Qt元对象系统:QMetaMethod
    行人重识别项目 | 基于Pytorch实现ReID行人重识别算法
    Redis快速度特性及为什么支持多线程及应用场景
    【Java Web】Spring整合Kafka
    NB的Github项目,看到最后一个我惊呆了!
    数据中台模块介绍
    Bean作用域和生命周期-------JavaEE(Spring)
    【408数据结构】第一章 绪论
    编译原生安卓aosp源码,实现硬改以及定位
    车载电子电器架构 —— 电气架构释放检查
  • 原文地址:https://www.cnblogs.com/jisuanjizhishizatan/p/16254842.html