zoukankan      html  css  js  c++  java
  • 小猪猪C++笔记基础篇(四)数组、指针、vector、迭代器

    小猪猪C++笔记基础篇(四)

    关键词:数组,Vector

     

    一、数组与指针

    数组相信大家学过C语言或者其他的语言都不陌生,简单的就是同一个变量类型的一组数据。例如:int a[10],意思就是从a开始有10个连续的int大小的空间。我们还是从初始化说起。

    我们以数据类型int为例,当然也可由有很多的数据类型,可以是像intdouble这种,也可以是自定义的类,一般的初始化方法有:

    int a[10];

    int a[10]={0};

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

    在前面的文章中,有的朋友提到了指针的运算,指针的运算在数组中是非常常见的。我们可以把a[10]看做10个连续的存储空间。那么a其实就是一个int型的指针,存放数组开始的位置,指向a[0]这个元素。那么容易知道a+1就是指向a[1]a+5就是指向a[5]了。于是根据指针的概念我们可以知道:

    *aa[0]是等价的,*(a+5)a[5]等价的,注意*(a+5)的括号位置,如果你写成*a+5那么结果等价于a[0]+5。写一小段代码测试一下:

    int a[6]={1,3,5,7,9,10};

    cout<<"a[0]: "<<a[0]<<"*a "<<*a<<endl;

    cout<<"a[5]: "<<a[5]<<" *(a+5)"<<*(a+5)<<"*a+5 "<<*a+5<<endl;

     

    结果就是这样的!

    那么前面文章中有朋友也提到了,a+1其实加的是步长,并且说a+1等价于a+sizeof(int)那么到底是不是这么回事呢。跟上上面这个初始化过的a,我们再做一个实验:

    cout<<sizeof(int)<<endl;

    cout<<a<<endl;

    cout<<a+1<<endl;

    cout<<a+sizeof(int)<<endl;

     

    int b[3]={0};

    int c[3]={0};

    cout<<b<<" "<<c<<" "<<b-c<<endl;

     

     

        从结果可以看到,sizeof(int)=4,一个B8bit,就是说我们这里的int 32位。

        第二行可以看到a[0]的地址是001FFDF0a[1]的地址是0001FFDF4,两个之间确实差4个也就是sizeof(int)这么大。 

    这个比较容易理解,当你把计算机每一位都理解成一个小盒子,一个int要占32个小盒子,那么下一个int的位置就是这么多了。

    那么实际上a+1a+sizeof(int)是不是一样的呢。可以看到a+sizeof(int)=0001FFE00,比a的值大了16,也就是说多了4*sizeof(int)这么大,所以说a+1a+sizeof(int)是不等价的,其实这也好解释,电脑是这么计算的,sizeof(int)=4a+4就等于这么多了。

    也就是说a的步长确实有sizeof(int)这么大,但是可不能写成a+sizeof(int)

     

    那我突然就很好奇,两个int *相减会怎么样,我就试了一下。可以发现b放的地址是001FFDDCc的地址是0001FFDC8。相减的结果是5,也就是5*sizeof(int)。这样就更加好解释为嘛a+1a+sizeof(int)是不一样的。我们想想啊,(a+1)-a=1,肯定不等于4

    这东西似乎并没有什么卵用,但是明确一下还是好的!

     

    接下来,既然我们知道了数组跟指针是有密切的联系,在实际开发中很多情况我们并知道到底需要用多大的数组,很多情况下我们都喜欢这么干:

    Int *a=new int[n];

    这实际上跟a[n]并没有什么区别,并且你在调用的时候依然可以用a[1],a[2]这么读取相应地址的元素。我们可以这么说,在数组中间的下标值跟指针后面加个数差不多的意思。所以就不能解释这种变态的问题了p[-2]是个神马东西!

    第一次碰到这个问题是在研究生复试的时候一个老师幽幽的问我,p是个数组,那p[-2]是多少?!这不是坑人么,长这么大突然发现数组的下标居然会是负数!!!但是老师果然就是吃的盐比我吃的饭要多,确实是这么回事,我们再写一下段代码来解释:

    int a[6]={1,3,5,7,9,10};

    int *p=&a[3];

    cout<<a[1]<<endl;

    cout<<p[-2]<<endl;

     

         P是一个int型指针,位置从a的第四个元素开始,也就是说p[0]=7,p[1]=9,p[2]=10,这样。很容易理解,p=a+3;那么p[-2]=*(p-2)=*(a+3-2)=*(a+1)=a[1]。也就是说p[-2]=*(p-2)。所以如果p-2指向的地址初始化过,那么p[-2]就等于p-2地址指向的值。这种用法是极少的,但是理论就是这样子的,以后万一给我当了老师也可以这么忽悠学生了!

        

    那么指针和数组这个问题我们也讲得差不多了,接下来还有一个很痛苦的问题:什么是指针数组和数组指针。

    解决这个问题我们先从多维数组来说起,多维数组就是例如int a[3][4]这样子的东西,实际上我们很少用,因为很容易出错。

    int a[3][4]意思就是有个大小为3的数组,每个元素是含有4个元素的数组。我们知道int a[3]里面这个a是一个指针int *,所以int a[3][4]a可以看成int(*p)[4]这种类型,就是指向有四个元素的指针。那么 int (*p)[4]是指向4个元素的数组,就是我们说的数组指针。P指向一个int *的指针,然后*p指向一个int 型。那么int *p[4]就是一个指针数组,元素放的是指针。实际上这些东西并没有什么用,而且非常容易搞错。还有的人用int **p来表示二维数组,int***p来表示三维数组,虽然是对的,但是看起来怕怕的。

    简单的总结一下:

    int (*p)[4]是数组指针,指向一个整数数组。调用元素的方法是(*p[i],值是整数。

    int  *p[4]是一个指针数组,存放的是指针,调用元素的方法是*p[i],值是指针。那实际上我们是怎么干的呢。两个方法:

    第一,做个别名:

    typedef int int_arry[4];

    int_arry a[3];

    第二,算坐标,比如我们申请int a[3][4],实际上可以申请一个int b[3*4]这么大的空间。a[2][3]=b[2*3+3];后面这种方法在图像处理中十分常见。

     

    二、vector和迭代器

    按道理来说,这个应该放在后面来讲的。但是它跟数组太像了,我在这里和数组做一个对比。

    Vector跟数组很像,我认为它和数组最主要的区别是vector并不需要事先知道要存的这么一串数据的大小,它的空间是动态分配的。我们做一个简单的对比。

    int a[6];

    vector<int>b;

     

    for(int i=0;i<6;i++)

    {

    a[i]=i;

    b.push_back(i);

    }

    for(int i=0;i<6;i++)

    {

    cout<<a[i]<<" ";

    }

    cout<<endl;

     

    for(int i=0;i<6;i++)

    {

    cout<<b[i]<<" ";

    }

    cout<<endl;

     

        这里都是把0-5这些数字存进去然后再打印出来。可以看到,vector存的时候是用.push_backi)来做的,而不是b[i],但是输出的时候跟数组是一样一样的操作。

         那么你会问为什么vector不能像数组一样直接调用下标赋值呢。这个很容易解释,因为数组预先开辟了空间,但是vector并没有预先开辟空间。Push_back这个操作其实是先申请一个空间,再把数据放进去。如果我们给没有初始化过的vector元素赋值,会报错,跟数组越界差不多的意思。

         但是如果vector的这个原始初始化过了,我们还是可以用b[i]=n,这样的跟数组一样的赋值。

     

         所以调用方法除了初始化部分其他基本一样。当你不确定数组大小的时候可以用vector来解决。当然还有一种替代的方法就是用指针,然后每增加一个元素就动态的申请一空间。具体做法可以参见《数据结构》中的队列或者栈中初始化的操作。

     

         然后我们再说一说迭代器。迭代器有点像指针,或者说指针就是迭代器的一种。当我们调用vec.begin()vec.end()返回的都是一个迭代器,类似于指针,取值方法也是带星号*vec.begin()这样。基本上指针怎么使,迭代器就怎么使。

        

         最后,再提一提那个负数下标的问题。在vector里面的下标只能值正整数,不能是负数,这是它跟指针的下标的区别。


    PS:改了名字,据说写逆袭博士容易遭喷,这其实也是给自己个动力,每天看看书,想想坚持个几年就可以毕业了。不过,还是改名字了。



  • 相关阅读:
    (Java实现) 洛谷 P1603 斯诺登的密码
    (Java实现) 洛谷 P1036 选数
    (Java实现) 洛谷 P1036 选数
    (Java实现) 洛谷 P1012 拼数
    (Java实现) 洛谷 P1012 拼数
    (Java实现) 洛谷 P1028 数的计算
    (Java实现) 洛谷 P1028 数的计算
    (Java实现) 洛谷 P1553 数字反转(升级版)
    8.4 确定两个日期之间的月份数或年数
    (Java实现) 洛谷 P1553 数字反转(升级版)
  • 原文地址:https://www.cnblogs.com/Dr-XLJ/p/4625429.html
Copyright © 2011-2022 走看看